最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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那個程序來達(dá)到相同效果體驗一下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):

這就是學(xué)技術(shù)特別有趣的地方,你看多了感覺很多高大上的技術(shù)底層設(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è)置失敗則走這個方法
acquire(1);
}
 

acquire(1);方法

public final voidacquire(int arg){
//這里if使用&&執(zhí)行了兩個方法,當(dāng)兩個條件都true時會執(zhí)行selfInterrupt();,也就是中斷當(dāng)前線程,這里可以猜測一下:這兩個方法應(yīng)該就是獲取鎖或者進(jìn)入等待隊列,當(dāng)都失敗時說明加鎖過程就失敗了,直接中斷線程結(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修飾,對所有線程都可見
int c =getState();
//當(dāng)state值為0時,說明此時持有鎖的線程已經(jīng)釋放鎖,此時就可以通過cas重新競爭鎖
if(c ==0){
if(compareAndSetState(0, acquires)){
setExclusiveOwnerThread(current);
returntrue;
}
}
//當(dāng)state值不為0時,判斷當(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)有一個node在AQS中時就不為null,不為null時把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時執(zhí)行
enq(node);
return node;
}
 

enq(node);方法

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
//當(dāng)tail為null時使head=tail=new Node()
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
            tail = head;
        } else {
//不為null時把node設(shè)置為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,類似于下圖:

當(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)前線程的前一個節(jié)點
final Node p = node.predecessor();
//如果前一個是head則會再執(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;
}
//如果前一個節(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) {
//獲取當(dāng)前線程前一個node的waitStatus值,waitStatus是volatile修飾的int,所以初始化值是0
int ws = pred.waitStatus;
//當(dāng)waitStatus是-1時,說明當(dāng)前node已經(jīng)給pred node設(shè)置過waitStatus了,直接返回true
if (ws == Node.SIGNAL)
return true;
//當(dāng)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設(shè)置成-1,-1實際上就是代表當(dāng)前node需要park,然后返回false
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
 

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

在設(shè)置成功后并不會執(zhí)行parkAndCheckInterrupt方法,而是再次進(jìn)入了一次循環(huán)嘗試獲取鎖,再沒有成功的話就執(zhí)行parkAndCheckInterrupt進(jìn)行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設(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)前線程不是擁有鎖的線程則報錯
    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變量的值,我們每次加鎖時都會增加state的值,所以在釋放鎖時就減小state的值,直到回到原始值0,說明已經(jīng)沒有線程持有鎖了。當(dāng)沒有線程持有鎖時就會嘗試喚醒等待線程

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的下一個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中的線程,此時就會進(jìn)入acquireQueued方法中的for循環(huán)再次嘗試獲取鎖
if (s != null)
LockSupport.unpark(s.thread);
}
 

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

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

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

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

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

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

相關(guān)文章

  • Java 堆內(nèi)存分區(qū)的實現(xiàn)示例

    Java 堆內(nèi)存分區(qū)的實現(xiàn)示例

    本文主要介紹了Java 堆內(nèi)存分區(qū)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • SpringDataJpa:JpaRepository增刪改查操作

    SpringDataJpa:JpaRepository增刪改查操作

    這篇文章主要介紹了SpringDataJpa:JpaRepository增刪改查操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • SpringBoot前后端json數(shù)據(jù)交互的全過程記錄

    SpringBoot前后端json數(shù)據(jù)交互的全過程記錄

    現(xiàn)在大多數(shù)互聯(lián)網(wǎng)項目都是采用前后端分離的方式開發(fā),下面這篇文章主要給大家介紹了關(guān)于SpringBoot前后端json數(shù)據(jù)交互的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-03-03
  • Java 十大排序算法之插入排序刨析

    Java 十大排序算法之插入排序刨析

    插入排序(InsertionSort),一般也被稱為直接插入排序。對于少量元素的排序,它是一個有效的算法。插入排序是一種最簡單的排序方法,它的基本思想是將一個記錄插入到已經(jīng)排好序的有序表中,從而一個新的、記錄數(shù)增 1 的有序表
    2021-11-11
  • java?web實現(xiàn)簡單登錄注冊功能全過程(eclipse,mysql)

    java?web實現(xiàn)簡單登錄注冊功能全過程(eclipse,mysql)

    前期我們學(xué)習(xí)了javaweb項目用JDBC連接數(shù)據(jù)庫,還有數(shù)據(jù)庫的建表功能,下面這篇文章主要給大家介紹了關(guān)于java?web實現(xiàn)簡單登錄注冊功能的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • SpringBoot使用Apache POI庫讀取Excel文件的操作詳解

    SpringBoot使用Apache POI庫讀取Excel文件的操作詳解

    在日常開發(fā)中,我們經(jīng)常需要處理Excel文件中的數(shù)據(jù),無論是從數(shù)據(jù)庫導(dǎo)入數(shù)據(jù)、處理數(shù)據(jù)報表,還是批量生成數(shù)據(jù),都可能會遇到需要讀取和操作Excel文件的場景,本文將詳細(xì)介紹如何使用Java中的Apache POI庫來讀取Excel文件,需要的朋友可以參考下
    2025-01-01
  • MybatisPlus處理四種表與實體的映射及id自增策略分析

    MybatisPlus處理四種表與實體的映射及id自增策略分析

    在最近的工作中,碰到一個比較復(fù)雜的返回結(jié)果,發(fā)現(xiàn)簡單映射已經(jīng)解決不了這個問題了,只好去求助百度,學(xué)習(xí)mybatis表與實體的映射應(yīng)該怎么寫,將學(xué)習(xí)筆記結(jié)合工作碰到的問題寫下本文,供自身查漏補(bǔ)缺,同時已被不時之需
    2022-10-10
  • 128進(jìn)制加密數(shù)據(jù)示例分享

    128進(jìn)制加密數(shù)據(jù)示例分享

    這篇文章主要介紹了128進(jìn)制加密數(shù)據(jù)示例,這里使用JAVA實現(xiàn),需要的朋友可以參考下
    2014-03-03
  • Java 對稱加密幾種算法分別實現(xiàn)

    Java 對稱加密幾種算法分別實現(xiàn)

    這篇文章主要介紹了Java 對稱加密使用DES / 3DES / AES 這三種算法分別實現(xiàn)的相關(guān)資料,這里提供了實例代碼,需要的朋友可以參考下
    2017-01-01
  • java實現(xiàn)字符串匹配求兩個字符串的最大公共子串

    java實現(xiàn)字符串匹配求兩個字符串的最大公共子串

    這篇文章主要介紹了java實現(xiàn)求兩個字符串最大公共子串的方法,詳細(xì)的描述了兩個字符串的最大公共子串算法的實現(xiàn),需要的朋友可以參考下
    2016-10-10

最新評論

长岛县| 大方县| 崇文区| 监利县| 攀枝花市| 巴里| 黔东| 于田县| 岫岩| 彝良县| 滦南县| 平顺县| 阿尔山市| 湖州市| 九江县| 封丘县| 江陵县| 汉源县| 长汀县| 呼玛县| 安图县| 赤壁市| 海城市| 黄平县| 贺州市| 松阳县| 繁昌县| 鄯善县| 卢氏县| 阜城县| 龙川县| 柏乡县| 隆德县| 北海市| 蛟河市| 雷波县| 曲阳县| 临清市| 阿克苏市| 台江县| 邯郸县|