深度解析Java中的ReentrantLock原理
在高并發(fā)編程中,AbstractQueuedSynchronizer(簡稱AQS)抽象的隊列同步器是我們必須掌握的,AQS底層提供了二種鎖模式
- 獨占鎖:ReentrantLock就是基于獨占鎖模式實現(xiàn)的
- 共享鎖:CountDownLatch,ReadWriteLock,Semplere都是基于共享鎖模式實現(xiàn)的
接下來我們通過ReentrantLock底層實現(xiàn)原理來了解AQS獨占鎖模式的實現(xiàn)原理
ReentrantLock用法
我們創(chuàng)建三個線程去獲取資源,然后執(zhí)行業(yè)務邏輯
ReentrantLock lock = new ReentrantLock(); new Thread(() -> { try{ lock.lock(); TimeUnit.SECONDS.sleep(10); }catch (Exception e){ }finally { lock.unlock(); } },"t1").start(); new Thread(() -> { try{ lock.lock(); TimeUnit.SECONDS.sleep(10); }catch (Exception e){ }finally { lock.unlock(); } },"t2").start(); new Thread(() -> { try{ lock.lock(); TimeUnit.SECONDS.sleep(10); }catch (Exception e){ }finally { lock.unlock(); } },"t3").start();
首先分析一下獲取鎖的原理,也就是 lock.lock()方法
public void lock() { //繼續(xù)進入這個方法 sync.lock(); }
進入這個方法的時候有兩個實現(xiàn)類,一個是公平鎖FairSync,還有一個就是非公平鎖NonfairSync,由于非公平鎖比公平鎖復雜,所以我們先分析非公平鎖的原理
final void lock() { //這里就會嘗試去獲取鎖,如果成功獲取到了鎖,此時state的值就會從0變成1 if (compareAndSetState(0, 1)) //就把exclusiveOwnerThread的值設(shè)置成當前線程 //exclusiveOwnerThread是AQS里面的一個變量,也就是線程獲取到了鎖之后,就會把這個值設(shè)置成當前線程 setExclusiveOwnerThread(Thread.currentThread()); else // 如果沒有獲取到鎖,就執(zhí)行下面這個方法 acquire(1); }
進入這個方法的時候有兩個實現(xiàn)類,一個是公平鎖FairSync,還有一個就是非公平鎖NonfairSync,由于非公平鎖比公平鎖復雜,所以我們先分析非公平鎖的原理
final void lock() { //這里就會嘗試去獲取鎖,如果成功獲取到了鎖,此時state的值就會從0變成1 if (compareAndSetState(0, 1)) //就把exclusiveOwnerThread的值設(shè)置成當前線程 //exclusiveOwnerThread是AQS里面的一個變量,也就是線程獲取到了鎖之后,就會把這個值設(shè)置成當前線程 setExclusiveOwnerThread(Thread.currentThread()); else // 如果沒有獲取到鎖,就執(zhí)行下面這個方法 acquire(1); }
第一個線程進來之后,由于state等于0,那么就可以獲取到鎖,于是就會把state的值設(shè)置成1,然后exclusiveOwnerThread值設(shè)置成自己的線程
這時候第二個線程進來,發(fā)現(xiàn)state的值已經(jīng)是1了,所以就會進入到acquire(1);這個方法了
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
這里一共有三個方法,我們先分析tryAcquire(arg);這里的參數(shù)值等于1
//還是進入到非公平鎖的實現(xiàn)類NonfairSync protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { //拿到當前線程 final Thread current = Thread.currentThread(); //獲取到state的值,由于之前已經(jīng)有線程獲取到鎖了,所以這個值現(xiàn)在等于1,也就不會進入帶if分支 int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //判斷此時獲取到鎖的線程是不是自己,這里就是可重入鎖的的實現(xiàn)原理了,但是我們是三個不同的線程 //所以也不會進入到這里 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //所以最后返回false return false; }
所以tryAcquire(arg)方法做了以下幾個步驟
- 1:繼續(xù)嘗試獲取鎖,如果獲取到鎖了就直接返回
- 2:如果沒有獲取到鎖,就判斷此時獲取到鎖的線程是不是自己,如果是,就可以獲取到資源繼續(xù)執(zhí)行,也就是可重入鎖的原理
- 3:如果以上二個步驟都不符合,就直接返回false
第一個tryAcquire(arg)方法最后返回false,但是這里是取返,所以是為true,所以就會進入acquireQueued()方法,但是在這里還有個addWaiter(Node.EXCLUSIVE), arg)方法,所以我們先分析這個方法
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //Node.EXCLUSIVE表示是獨占模式 selfInterrupt(); }
private Node addWaiter(Node mode) { //將當前線程封裝成一個Node節(jié)點,并設(shè)置成獨占模式, //注意:一個新的Node節(jié)點的waitStatus的值等于0 Node node = new Node(Thread.currentThread(), mode); //在第一次進來的時候,tail和head都是null,所以不會進入到if分支里面去 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //由于head和tail都為null,所以會進入初始化鏈表的方法 //初始化鏈表 enq(node); return node; }
private Node enq(final Node node) { for (;;) { //注意:這里是死循環(huán) Node t = tail; //第一次進來,因為tail=null,所以會進入到if里面去 if (t == null) { // Must initialize //這里新創(chuàng)建一個空的Node節(jié)點 if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
- 第一次進來:因為第一次進來的時候tail=null,所以會進入到if中去,然后創(chuàng)建一個新的空的節(jié)點,然后將頭節(jié)點和尾節(jié)點都指向這個節(jié)點
- 然后進入第二次循環(huán):這時候tail已經(jīng)不為空了,所以會進入到else分支里面去,所以的操作就是將當前線程封裝成的Node設(shè)置尾巴節(jié)點,然后設(shè)置前置節(jié)點和后置節(jié)點的關(guān)系
這時候第三個線程進來,然后假設(shè)也沒有獲取到鎖,那么也會進入到addWaiter()方法中
private Node addWaiter(Node mode) { //將當前線程封裝成一個Node節(jié)點,并設(shè)置成獨占模式, //注意:一個新的Node節(jié)點的waitStatus的值等于0 Node node = new Node(Thread.currentThread(), mode); //這時候tail和head都不為null了,所以就會進入if分支 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //由于tail和head都不為null,也就不會執(zhí)行到這里來了 enq(node); return node; }
這時候新進來的線程就會從鏈表尾部插入,然后重新更新tail尾節(jié)點指向當前線程
這時候addWaiter(Node.EXCLUSIVE), arg);就會返回一個Node節(jié)點了,這個Node節(jié)點就是當前封裝了當前線程的Node,比如現(xiàn)在第一個線程Thread-1獲取到鎖了,然后Thread-2進來,那么Thread-2就要進入隊列中進行等待了,所以這時候返回的就是Thread-2這個Node節(jié)點。同理,第三個線程Thread-3進來,那么這時候返回的就是Thread-3這個Node節(jié)點
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //Node.EXCLUSIVE表示是獨占模式 selfInterrupt(); }
現(xiàn)在我們先分析第二個線程Thread-2。進入acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) { //此時Node節(jié)點就是Thread-2這個線程的Node boolean failed = true; try { boolean interrupted = false; for (;;) { //拿到上一個節(jié)點,因為Thread-2是第一個排隊的線程,所以他的前置節(jié)點就是頭節(jié)點 final Node p = node.predecessor(); //p是頭節(jié)點,所以會進入tryAcquire(arg)方法,這個方法就是再次去嘗試獲取鎖 //但是頭節(jié)點就是個虛擬節(jié)點,是不可能獲取到鎖的,所以不會進入到if分支里面去 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //此時會進入到這個分支里面去,此時的p是頭節(jié)點,node是Thread-2的Node節(jié)點 //在第二次循環(huán)過后,shouldParkAfterFailedAcquire(p, node)會返回true if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
//第一次進來,由于頭節(jié)點是剛初始化的,所以waitStatus=0,所以會進入到else分支,修改頭節(jié)點waitStatus //然后由于外面的是死循環(huán),所以會再次進入,此時頭節(jié)點的waitStatus的值是Node.SIGNAL,所以會直接返回true private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //將頭節(jié)點的waitStatus修改成Node.SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
由于shouldParkAfterFailedAcquire(p, node)返回true了,所以就會繼續(xù)執(zhí)行 parkAndCheckInterrupt()方法了
private final boolean parkAndCheckInterrupt() { //將Thread-2線程阻塞掛起,這里為什么是this呢,因為現(xiàn)在執(zhí)行進來的就是Thread-2線程 LockSupport.park(this); return Thread.interrupted(); }
后續(xù)Thread-3線程進來之后,也會在這里阻塞住,然后等待被喚醒
現(xiàn)在Thread-1線程業(yè)務執(zhí)行結(jié)束了,然后就要釋放鎖
lock.unlock();
public void unlock() { sync.release(1); }
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
我們首先看下tryRelease(arg)方法
protected final boolean tryRelease(int releases) { //將state的值減去1 int c = getState() - releases; //判斷當前線程是否是獲取鎖的那個線程 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //如果state的值減1之后變成0了,那么就將exclusiveOwnerThread設(shè)置成null, //因為我們state的值就是等于1,所以c=0,就會進入if分支里面去 if (c == 0) { free = true; setExclusiveOwnerThread(null); } //將state的值設(shè)置成c setState(c); //因為會進入if分支,所以此時free的值等于true return free; }
所以tryRelease(arg)最后會返回true,于是就會進入if分支里面去
public final boolean release(int arg) { if (tryRelease(arg)) { //返回true //拿到鏈表的頭節(jié)點 Node h = head; //雖然在初始化的時候Node節(jié)點的waitStatus的值等于0,但是后面進來的線程會將前置節(jié)點的該值修改成-1 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
//node是頭節(jié)點 private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //拿到頭節(jié)點的下一個節(jié)點,也就是Thread-2線程的節(jié)點 Node s = node.next; 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; } if (s != null) //最后喚醒Thread-2線程繼續(xù)執(zhí)行吧 LockSupport.unpark(s.thread); }
Thread-2之前是在acquireQueued()方法中被阻塞住的
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); //當再次循環(huán)到這里來的時候,Thread-2的前置節(jié)點就是頭節(jié)點,并且此時Thread-1線程 //已經(jīng)釋放鎖了,所以此時tryAcquire(arg)嘗試去獲取鎖就能成功,所以會執(zhí)行if分支 if (p == head && tryAcquire(arg)) { //將Thread-2的Node節(jié)點設(shè)置成頭節(jié)點 setHead(node); //之前的頭節(jié)點都設(shè)置成null,可以被GC回收 p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && //Thread-2阻塞在這里,被喚醒之后就會繼續(xù)執(zhí)行這個循環(huán) parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
所以最后Thread-2線程釋放鎖之后,此時鏈表的頭節(jié)點是Thread-2的Node節(jié)點了,Thread-2線程釋放鎖之后,也會喚醒Thread-3線程,然后繼續(xù)執(zhí)行。
以上就是深度解析Java中的ReentrantLock原理的詳細內(nèi)容,更多關(guān)于Java ReentrantLock原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot Redis批量存取數(shù)據(jù)的操作
這篇文章主要介紹了SpringBoot Redis批量存取數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08MyBatis?多表聯(lián)合查詢及優(yōu)化方法
大家都知道Hibernate 是全自動的數(shù)據(jù)庫持久層框架,它可以通過實體來映射數(shù)據(jù)庫,通過設(shè)置一對多、多對一、一對一、多對多的關(guān)聯(lián)來實現(xiàn)聯(lián)合查詢,接下來通過本文給大家介紹MyBatis?多表聯(lián)合查詢及優(yōu)化,需要的朋友可以參考下2022-08-08Java數(shù)組,去掉重復值、增加、刪除數(shù)組元素的方法
下面小編就為大家?guī)硪黄狫ava數(shù)組,去掉重復值、增加、刪除數(shù)組元素的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10Spring框架JavaMailSender發(fā)送郵件工具類詳解
這篇文章主要為大家詳細介紹了Spring框架JavaMailSender發(fā)送郵件工具類,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04SpringCloud?Feign集成AOP的常見問題與解決
在使用?Spring?Cloud?Feign?作為微服務通信的工具時,我們可能會遇到?AOP?不生效的問題,這篇文章將深入探討這一問題,給出幾種常見的場景,分析可能的原因,并提供解決方案,希望對大家有所幫助2023-10-10