欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java可重入鎖reentrantLock解析

 更新時間:2023年12月28日 08:59:49   作者:竹下星空  
這篇文章主要介紹了Java可重入鎖reentrantLock解析,reentrantLock跟synchronized代碼結(jié)構(gòu)差不多,只是多了一個lock和unlock的過程,需要的朋友可以參考下

Java可重入鎖reentrantLock

面試思考:

重入的概念是什么,實現(xiàn)原理是什么?

公平鎖和非公平鎖區(qū)別是什么,體現(xiàn)在哪些地方?

沒有競爭到鎖的線程怎么實現(xiàn)等待,釋放鎖時又是怎么被喚醒的?

我們先用reentrantLock改造一下synchronized那個程序來達到相同效果體驗一下lock:

package com.example.demo.service;
import lombok.SneakyThrows;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLockDemo {
	static int flag = 0;
	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
		new Thread("線程1") {
			@SneakyThrows
			public void run() {
				while(flag<10) {
					lock.lock();
					try {
						System.out.println(Thread.currentThread().getName()+"打?。?+(++flag));
						condition.signal();
						condition.await();
					}finally {
						lock.unlock();
					}
				}
			};
		}.start();
		new Thread("線程2") {
			@SneakyThrows
			public void run() {
				while(flag<10) {
					lock.lock();
					try {
						System.out.println(Thread.currentThread().getName()+"打印:"+(++flag));
						condition.signal();
						condition.await();
					}finally {
						lock.unlock();
					}
				}
			};
		}.start();
	}
}
 

發(fā)現(xiàn)沒有是不是跟synchronized代碼結(jié)構(gòu)差不多,只是多了一個lock和unlock的過程。我們先畫個圖來對比一下synchronized和lock的數(shù)據(jù)結(jié)構(gòu):

這就是學技術(shù)特別有趣的地方,你看多了感覺很多高大上的技術(shù)底層設計實際都差不多。下面我們具體看下reentrantLock加鎖過程:

publicReentrantLock(){
    sync =newNonfairSync();
}

ReentrantLock默認是非公平鎖。

lock方法

final voidlock(){
//先通過cas把state從0設置成1
if(compareAndSetState(0,1))
//如果設置成功說明當前線程獲取了鎖,把鎖擁有者設置成當前線程
setExclusiveOwnerThread(Thread.currentThread());
else
設置失敗則走這個方法
acquire(1);
}
 

acquire(1);方法

public final voidacquire(int arg){
//這里if使用&&執(zhí)行了兩個方法,當兩個條件都true時會執(zhí)行selfInterrupt();,也就是中斷當前線程,這里可以猜測一下:這兩個方法應該就是獲取鎖或者進入等待隊列,當都失敗時說明加鎖過程就失敗了,直接中斷線程結(jié)束流程。
if(!tryAcquire(arg)&&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
 

nonfairTryAcquire(arg)方法

final booleannonfairTryAcquire(int acquires){
//獲取當前線程
    final Thread current = Thread.currentThread();
//獲取state的值,state是被volatile修飾,對所有線程都可見
int c =getState();
//當state值為0時,說明此時持有鎖的線程已經(jīng)釋放鎖,此時就可以通過cas重新競爭鎖
if(c ==0){
if(compareAndSetState(0, acquires)){
setExclusiveOwnerThread(current);
returntrue;
}
}
//當state值不為0時,判斷當前線程是否是鎖持有線程,是則把state設置成原值+acquires,獲取鎖成功
elseif(current ==getExclusiveOwnerThread()){
int nextc = c + acquires;
if(nextc <0)// overflow
thrownewError("Maximum lock count exceeded");
setState(nextc);
returntrue;
}
//否則獲取鎖失敗
returnfalse;
}

nonfairTryAcquire方法主要是想看看已經(jīng)獲得鎖的線程是不是當前線程,是的話可以再次獲取到鎖,體現(xiàn)了可重入的特性。不是的話就需要執(zhí)行acquireQueued方法

addWaiter(Node mode)方法

private Node addWaiter(Node mode) {
//把當前線程封裝成node,nextWaiter=EXCLUSIVE= null
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//判斷當前AQS的tail是否是null,剛開始肯定是null,但當已經(jīng)有一個node在AQS中時就不為null,不為null時把node設置為tail,pred的next指針指向node,node的prev指針指向pred
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//tail為null時執(zhí)行
enq(node);
return node;
}
 

enq(node);方法

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
//當tail為null時使head=tail=new Node()
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
            tail = head;
        } else {
//不為null時把node設置為tail,pred的next指針指向node,node的prev指針指向pred,并跳出死循環(huán)
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
                }
            }
    }
}

addWaiter方法實際就是初始化了AQS中的雙向鏈表,head和tail開始指向的是一個沒有線程的空node,之后把封裝了線程的EXCLUSIVE node放入雙向鏈表中,tail指向EXCLUSIVE node,head指向沒有線程的空node,類似于下圖:

當封裝好node并進入AQS的雙向鏈表之后進入acquireQueued方法:

acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
獲取當前線程的前一個節(jié)點
final Node p = node.predecessor();
//如果前一個是head則會再執(zhí)行nonfairTryAcquire方法進行獲取鎖,如果獲取成功
if (p == head && tryAcquire(arg)) {
把當前線程的node設置成head,thead=null,prev=null
setHead(node);
把之前的head從雙向鏈表中斷開引用,讓gc回收
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果前一個節(jié)點不是head或者獲取鎖失敗則執(zhí)行if中的方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
 

shouldParkAfterFailedAcquire(p, node) 方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//獲取當前線程前一個node的waitStatus值,waitStatus是volatile修飾的int,所以初始化值是0
int ws = pred.waitStatus;
//當waitStatus是-1時,說明當前node已經(jīng)給pred node設置過waitStatus了,直接返回true
if (ws == Node.SIGNAL)
return true;
//當waitStatus大于0時,實際上就是1,1代表CANCELLED,就是已經(jīng)終止了的node,所以用while直接跳過所有終止的node,并返回false
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果即不等于-1也不是1,就把pred設置成-1,-1實際上就是代表當前node需要park,然后返回false
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
 

shouldParkAfterFailedAcquire方法總結(jié)一下就是把當前node的上一個node的waitStatus設置成-1,但waitStatus前值不能是大于0的,因為大于0代表node已經(jīng)終止了。

在設置成功后并不會執(zhí)行parkAndCheckInterrupt方法,而是再次進入了一次循環(huán)嘗試獲取鎖,再沒有成功的話就執(zhí)行parkAndCheckInterrupt進行park,然后for循環(huán)就卡住,整個流程如下:

可以看出java的lock實際就是借助LockSupport.park(this);方法來實現(xiàn),沒有獲取到鎖的線程在for循環(huán)中park住,等待持有鎖的線程來unpark喚醒他們。那么接下來看看unlock方法是怎么喚醒park線程的。

release方法

public final boolean release(int arg) {
//先執(zhí)行tryRelease方法,tryRelease方法返回true繼續(xù)執(zhí)行if邏輯
if (tryRelease(arg)) {
Node h = head;
//head不為null并且waitStatus不為0時會執(zhí)行unparkSuccessor方法,我們知道waitStatus初始值是0,并且在shouldParkAfterFailedAcquire方法時把waitStatus設置為-1
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
 

tryRelease(arg)方法

protected final boolean tryRelease(int releases) {
//當前state減去releases值獲取c
    int c = getState() - releases;
//當前線程不是擁有鎖的線程則報錯
    if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
    boolean free = false;
//如果為0則說明沒有線程持有鎖,則把擁有鎖的線程設置成null
    if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
    }
//把status設置成c
    setState(c);
//返回是否沒有持有鎖的線程
    return free;
}
 

tryRelease方法就是在處理state變量的值,我們每次加鎖時都會增加state的值,所以在釋放鎖時就減小state的值,直到回到原始值0,說明已經(jīng)沒有線程持有鎖了。當沒有線程持有鎖時就會嘗試喚醒等待線程

unparkSuccessor(h);方法

private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//先拿到waitStatus,如果小于0,則把他設置成初始值0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
//獲取node的下一個node,如果s是null或則waitStatus大于0.則用for循環(huán)從tail往前找waitStatus小于等于0的node,直到找到最前面一個waitStatus小于等于0的node
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果找到了則喚醒node中的線程,此時就會進入acquireQueued方法中的for循環(huán)再次嘗試獲取鎖
if (s != null)
LockSupport.unpark(s.thread);
}
 

現(xiàn)在這個非公平鎖上鎖和釋放鎖的過程都比較清晰了,我們來總結(jié)一下:lock時會先嘗試把state從0設置成1,成功則獲取鎖成功,失敗則會判斷持有鎖的線程是不是當前線程,是則獲取鎖成功,不是則會被封裝成EXCLUSIVE的node節(jié)點放入AQS的雙向鏈表等待隊列中,然后再一次判斷自己是不是第一個等待線程,是的話會再次嘗試獲取鎖,如果還是失敗則park,等待持有鎖的線程釋放鎖之后unpark喚醒。

為什么head節(jié)點是一個空節(jié)點呢

  • 如果head不是空節(jié)點,在非公平鎖時p == head && tryAcquire(arg)這個判斷就有問題,因為即使當前節(jié)點的前一個節(jié)點是head,那也不能去獲取鎖,因為此時head是有線程的,head線程可能都沒執(zhí)行完或自己都是park狀態(tài),這時候獲取鎖一點意義沒有。
  • 如果head不是空節(jié)點,那么在unlock時就不能用head去喚醒下一個node,此時要有更復雜的邏輯去維護各個node 狀態(tài),而且在線程被喚醒后還要維護waitStatus值,這就加大了代碼復雜性

非公平鎖相對于公平鎖來說非公平體現(xiàn)在哪里

  • 在lock時非公平鎖會直接嘗試cas改變state來獲取鎖,而不是執(zhí)行tryAcquire方法
  • 在執(zhí)行tryAcquire方法時非公平鎖在state是0時會直接嘗試cas改變state來獲取鎖,不用判斷AQS中有沒有線程在等待
  • 在執(zhí)行acquireQueued方法執(zhí)行tryAcquire方法也會進行一次2中的過程

到此這篇關于Java可重入鎖reentrantLock解析的文章就介紹到這了,更多相關可重入鎖reentrantLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java?中泛型?T?和???的區(qū)別詳解

    Java?中泛型?T?和???的區(qū)別詳解

    本文主要介紹了Java?中泛型?T?和???的區(qū)別,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 根據(jù)ID填充文本框的實例代碼

    根據(jù)ID填充文本框的實例代碼

    這篇文章介紹了根據(jù)ID填充文本框的小例子,有需要的朋友可以參考一下
    2013-07-07
  • Intellij Idea中進行Mybatis逆向工程的實現(xiàn)

    Intellij Idea中進行Mybatis逆向工程的實現(xiàn)

    這篇文章主要介紹了Intellij Idea中進行Mybatis逆向工程的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-05-05
  • 基于Java實現(xiàn)計數(shù)排序,桶排序和基數(shù)排序

    基于Java實現(xiàn)計數(shù)排序,桶排序和基數(shù)排序

    這篇文章主要為大家詳細介紹了計數(shù)排序,桶排序和基數(shù)排序的多種語言的實現(xiàn)(JavaScript、Python、Go語言、Java),感興趣的小伙伴可以了解一下
    2022-12-12
  • SpringBoot中的Condition包下常用條件依賴注解案例介紹

    SpringBoot中的Condition包下常用條件依賴注解案例介紹

    這篇文章主要介紹了SpringBoot中的Condition包下常用條件依賴注解案例,文章基于Java的相關資料展開主題詳細內(nèi)容,需要的小伙伴可以參考一下
    2022-04-04
  • java線程池實現(xiàn)批量下載文件

    java線程池實現(xiàn)批量下載文件

    這篇文章主要為大家詳細介紹了java線程池實現(xiàn)批量下載文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Java String轉(zhuǎn)換時為null的解決方法

    Java String轉(zhuǎn)換時為null的解決方法

    這篇文章主要介紹了Java String轉(zhuǎn)換時為null的解決方法,需要的朋友可以參考下
    2017-07-07
  • 最新評論