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

Java可重入鎖reentrantLock解析

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

Java可重入鎖reentrantLock

面試思考:

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

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

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

我們先用reentrantLock改造一下synchronized那個(gè)程序來達(dá)到相同效果體驗(yàn)一下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)差不多,只是多了一個(gè)lock和unlock的過程。我們先畫個(gè)圖來對(duì)比一下synchronized和lock的數(shù)據(jù)結(jié)構(gòu):

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

publicReentrantLock(){
    sync =newNonfairSync();
}

ReentrantLock默認(rèn)是非公平鎖。

lock方法

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

acquire(1);方法

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

nonfairTryAcquire(arg)方法

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

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

addWaiter(Node mode)方法

private Node addWaiter(Node mode) {
//把當(dāng)前線程封裝成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;
//判斷當(dāng)前AQS的tail是否是null,剛開始肯定是null,但當(dāng)已經(jīng)有一個(gè)node在AQS中時(shí)就不為null,不為null時(shí)把node設(shè)置為tail,pred的next指針指向node,node的prev指針指向pred
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//tail為null時(shí)執(zhí)行
enq(node);
return node;
}
 

enq(node);方法

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

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

當(dāng)封裝好node并進(jìn)入AQS的雙向鏈表之后進(jìn)入acquireQueued方法:

acquireQueued方法

final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
獲取當(dāng)前線程的前一個(gè)節(jié)點(diǎn)
final Node p = node.predecessor();
//如果前一個(gè)是head則會(huì)再執(zhí)行nonfairTryAcquire方法進(jìn)行獲取鎖,如果獲取成功
if (p == head && tryAcquire(arg)) {
把當(dāng)前線程的node設(shè)置成head,thead=null,prev=null
setHead(node);
把之前的head從雙向鏈表中斷開引用,讓gc回收
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果前一個(gè)節(jié)點(diǎn)不是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) {
//獲取當(dāng)前線程前一個(gè)node的waitStatus值,waitStatus是volatile修飾的int,所以初始化值是0
int ws = pred.waitStatus;
//當(dāng)waitStatus是-1時(shí),說明當(dāng)前node已經(jīng)給pred node設(shè)置過waitStatus了,直接返回true
if (ws == Node.SIGNAL)
return true;
//當(dāng)waitStatus大于0時(shí),實(shí)際上就是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設(shè)置成-1,-1實(shí)際上就是代表當(dāng)前node需要park,然后返回false
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
 

shouldParkAfterFailedAcquire方法總結(jié)一下就是把當(dāng)前node的上一個(gè)node的waitStatus設(shè)置成-1,但waitStatus前值不能是大于0的,因?yàn)榇笥?代表node已經(jīng)終止了。

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

可以看出java的lock實(shí)際就是借助LockSupport.park(this);方法來實(shí)現(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時(shí)會(huì)執(zhí)行unparkSuccessor方法,我們知道waitStatus初始值是0,并且在shouldParkAfterFailedAcquire方法時(shí)把waitStatus設(shè)置為-1
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
 

tryRelease(arg)方法

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

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

unparkSuccessor(h);方法

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

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

為什么head節(jié)點(diǎn)是一個(gè)空節(jié)點(diǎn)呢

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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