摘要:內(nèi)部提供了兩種的實(shí)現(xiàn),一種公平模式,一種是非公平模式,如果沒有特別指定在構(gòu)造器中,默認(rèn)是非公平的模式,我們可以看一下無參的構(gòu)造函數(shù)。
概述
并發(fā)編程中,ReentrantLock的使用是比較多的,包括之前講的LinkedBlockingQueue和ArrayBlockQueue的內(nèi)部都是使用的ReentrantLock,談到它又不能的不說AQS,AQS的全稱是AbstractQueuedSynchronizer,這個(gè)類也是在java.util.concurrent.locks下面,提供了一個(gè)FIFO的隊(duì)列,可以用于構(gòu)建鎖的基礎(chǔ)框架,內(nèi)部通過原子變量state來表示鎖的狀態(tài),當(dāng)state大于0的時(shí)候表示鎖被占用,如果state等于0時(shí)表示沒有占用鎖,ReentrantLock是一個(gè)重入鎖,表現(xiàn)在state上,如果持有鎖的線程重復(fù)獲取鎖時(shí),它會(huì)將state狀態(tài)進(jìn)行遞增,也就是獲得一個(gè)信號(hào)量,當(dāng)釋放鎖時(shí),同時(shí)也是釋放了信號(hào)量,信號(hào)量跟隨減少,如果上一個(gè)線程還沒有完成任務(wù),則會(huì)進(jìn)行入隊(duì)等待操作。
本文分析內(nèi)容主要是針對(duì)jdk1.8版本AQS主要字段約束:文中圖片的ref-xxx代表引用地址
圖片中的內(nèi)容prve更正為prev,由于文章不是一天寫的所以有些圖片更正了有些沒有。
/** * 頭節(jié)點(diǎn)指針,通過setHead進(jìn)行修改 */ private transient volatile Node head; /** * 隊(duì)列的尾指針 */ private transient volatile Node tail; /** * 同步器狀態(tài) */ private volatile int state;AQS需要子類實(shí)現(xiàn)的方法
AQS是提供了并發(fā)的框架,它內(nèi)部提供一種機(jī)制,它是基于模板方法的實(shí)現(xiàn),整個(gè)類中沒有任何一個(gè)abstract的抽象方法,取而代之的是,需要子類去實(shí)現(xiàn)的那些方法通過一個(gè)方法體拋出UnsupportedOperationException異常來讓子類知道,告知如果沒有實(shí)現(xiàn)模板的方法,則直接拋出異常。
方法名 | 方法描述 |
---|---|
tryAcquire | 以獨(dú)占模式嘗試獲取鎖,獨(dú)占模式下調(diào)用acquire,嘗試去設(shè)置state的值,如果設(shè)置成功則返回,如果設(shè)置失敗則將當(dāng)前線程加入到等待隊(duì)列,直到其他線程喚醒 |
tryRelease | 嘗試獨(dú)占模式下釋放狀態(tài) |
tryAcquireShared | 嘗試在共享模式獲得鎖,共享模式下調(diào)用acquire,嘗試去設(shè)置state的值,如果設(shè)置成功則返回,如果設(shè)置失敗則將當(dāng)前線程加入到等待隊(duì)列,直到其他線程喚醒 |
tryReleaseShared | 嘗試共享模式下釋放狀態(tài) |
isHeldExclusively | 是否是獨(dú)占模式,表示是否被當(dāng)前線程占用 |
AQS是基于FIFO隊(duì)列實(shí)現(xiàn)的,那么隊(duì)列的Node節(jié)點(diǎn)又是存放的什么呢?
Node字段信息字段名 | 類型 | 默認(rèn)值 | 描述 |
---|---|---|---|
SHARED | Node | new Node() | 一個(gè)標(biāo)識(shí),指示節(jié)點(diǎn)使用共享模式等待 |
EXCLUSIVE | Nodel | Null | 一個(gè)標(biāo)識(shí),指示節(jié)點(diǎn)使用獨(dú)占模式等待 |
CANCELLED | int | 1 | 節(jié)點(diǎn)因超時(shí)或被中斷而取消時(shí)設(shè)置狀態(tài)為取消狀態(tài) |
SIGNAL | int | -1 | 當(dāng)前節(jié)點(diǎn)的后節(jié)點(diǎn)被park,當(dāng)前節(jié)點(diǎn)釋放時(shí),必須調(diào)用unpark通知后面節(jié)點(diǎn),當(dāng)后面節(jié)點(diǎn)競爭時(shí),會(huì)將前面節(jié)點(diǎn)更新為SIGNAL |
CONDITION | int | -2 | 標(biāo)識(shí)當(dāng)前節(jié)點(diǎn)已經(jīng)處于等待中,通過條件進(jìn)行等待的狀態(tài) |
PROPAGATE | int | -3 | 共享模式下釋放節(jié)點(diǎn)時(shí)設(shè)置的狀態(tài),被標(biāo)記為當(dāng)前狀態(tài)是表示無限傳播下去 |
0 | int | 不屬于上面的任何一種狀態(tài) | |
waitStatus | int | 0 | 等待狀態(tài),默認(rèn)初始化為0,表示正常同步等待, |
pre | Node | Null | 隊(duì)列中上一個(gè)節(jié)點(diǎn) |
next | Node | Null | 隊(duì)列中下一個(gè)節(jié)點(diǎn) |
thread | Thread | Null | 當(dāng)前Node操作的線程 |
nextWaiter | Node | Null | 指向下一個(gè)處于阻塞的節(jié)點(diǎn) |
通過上面的內(nèi)容我們可以看到waitStatus其實(shí)是有5個(gè)狀態(tài)的,雖然這里面0并不是什么字段,但是他是waitStatus狀態(tài)的一種,表示不是任何一種類型的字段,上面也講解了關(guān)于AQS中子類實(shí)現(xiàn)的方法,AQS提供了獨(dú)占模式和共享模式兩種,但是ReentrantLock實(shí)現(xiàn)的是獨(dú)占模式的方式,下面來通過源碼的方式解析ReentrantLock。
ReentrantLock源碼分析首先在源碼分析之前我們先來看一下ReentrantLock的類的繼承關(guān)系,如下圖所示:
可以看到ReentrantLock繼承自Lock接口,它提供了一些獲取鎖和釋放鎖的方法,以及條件判斷的獲取的方法,通過實(shí)現(xiàn)它來進(jìn)行鎖的控制,它是顯示鎖,需要顯示指定起始位置和終止位置,Lock接口的方法介紹:
方法名稱 | 方法描述 |
---|---|
lock | 用來獲取鎖,如果鎖已被其他線程獲取,則進(jìn)行等待。 |
tryLock | 表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲?。?,則返回false,也就說這個(gè)方法無論如何都會(huì)立即返回。在拿不到鎖時(shí)不會(huì)一直在那等待 |
tryLock(long time, TimeUnit unit) | 和tryLock()類似,區(qū)別在于它在拿不到鎖時(shí)會(huì)等待一定的時(shí)間,在時(shí)間期限之內(nèi)如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true |
lockInterruptibly | 獲取鎖,如果獲取鎖失敗則進(jìn)行等到,如果等待的線程被中斷會(huì)相應(yīng)中斷信息。 |
unlock | 釋放鎖的操作 |
newCondition | 獲取Condition對(duì)象,該組件和當(dāng)前的鎖綁定,當(dāng)前線程只有獲得了鎖,才能調(diào)用該組件wait()方法,而調(diào)用后,當(dāng)前線程釋放鎖。 |
ReentrantLock也實(shí)現(xiàn)了上面接口的內(nèi)容,前面講解了很多理論行的內(nèi)容,接下來我們以一個(gè)簡單的例子來進(jìn)行探討
public class ReentrantLockDemo { public static void main(String[] args) throws Exception { AddDemo runnalbeDemo = new AddDemo(); Thread thread = new Thread(runnalbeDemo::add); thread.start(); Thread thread1 = new Thread(runnalbeDemo::add); thread1.start(); Thread.sleep(1000); System.out.println(runnalbeDemo.getCount()); } private static class AddDemo { private final AtomicInteger count = new AtomicInteger(); private final ReentrantLock reentrantLock = new ReentrantLock(); private void add() { try { reentrantLock.lock(); count.getAndIncrement(); } finally { // reentrantLock.unlock(); } } int getCount() { return count.get(); } } }
首先聲明內(nèi)部類AddDemo,AddDemo的主要作用是將原子變量count進(jìn)行遞增的操作
AddDemo內(nèi)部聲明了ReentrantLock對(duì)象進(jìn)行同步操作
AddDemo的add方法,進(jìn)行遞增操作,細(xì)心地同學(xué)發(fā)現(xiàn),使用了lock方法獲取鎖,但是沒有釋放鎖,這里面沒有釋放鎖可以更讓我們清晰的分析內(nèi)部結(jié)構(gòu)的變化。
主線程開啟了兩個(gè)線程進(jìn)行同步進(jìn)行遞增的操作,最后讓線程休眠一會(huì)輸出累加的最后結(jié)果。
ReentrantLock內(nèi)部提供了兩種AQS的實(shí)現(xiàn),一種公平模式,一種是非公平模式,如果沒有特別指定在構(gòu)造器中,默認(rèn)是非公平的模式,我們可以看一下無參的構(gòu)造函數(shù)。
public ReentrantLock() { sync = new NonfairSync(); }
當(dāng)調(diào)用有參構(gòu)造函數(shù)時(shí),指定使用哪種模式來進(jìn)行操作,參數(shù)為布爾類型,如果指定為false的話代表非公平模式,如果指定為true的話代表的是公平模式,如下所示:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
我們使用的是非公平模式,后面再來進(jìn)行分析公平模式,上面也講到了分為兩種模式,這兩種模式為FairSync和NonfairSync兩個(gè)內(nèi)部靜態(tài)類不可變類,不能被繼承和實(shí)例化,這兩個(gè)類是我們今天分析的重點(diǎn),為什么說是重點(diǎn)呢,這里講的內(nèi)容是有關(guān)于AQS的,而FairSync和NonfairSync實(shí)現(xiàn)了抽象內(nèi)部類Sync,Sync實(shí)現(xiàn)了AbstractQueuedSynchronizer這個(gè)類,這個(gè)類就是我們說的AQS也是主要同步操作的類,下面我們來看一下公平模式和非公平模式下類的繼承關(guān)系,如下圖所示:
非公平模式:
公平模式:
通過上面兩個(gè)繼承關(guān)系UML來看其實(shí)無差別,差別在于內(nèi)部實(shí)現(xiàn)的原理不一樣,回到上面例子中使用的是非公平模式,那先以非公平模式來進(jìn)行分析,
假設(shè)第一個(gè)線程啟動(dòng)調(diào)用AddDemo的add方法時(shí),首先執(zhí)行的事reentrantLock.lock()方法,這個(gè)lock方法調(diào)用了sync.lock(),sync就是我們上面提到的兩種模式的對(duì)象,來看一下源碼內(nèi)容:
public void lock() { sync.lock(); }
內(nèi)部調(diào)用了sync.lock(),其實(shí)是調(diào)用了NonfairSync對(duì)象的lock方法,也就是下面的方法內(nèi)容。
/** * 非公平模式鎖 */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * 執(zhí)行鎖動(dòng)作,先進(jìn)行修改狀態(tài),如果鎖被占用則進(jìn)行請(qǐng)求申請(qǐng)鎖,申請(qǐng)鎖失敗則將線程放到隊(duì)列中 */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // 繼承自AQS的tryAcquire方法,嘗試獲取鎖操作,這個(gè)方法會(huì)被AQS的acquire調(diào)用 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
我們看到lock方法首先先對(duì)state狀態(tài)進(jìn)行修改操作,如果鎖沒有被占用則獲取鎖,并設(shè)置當(dāng)前線程獨(dú)占鎖資源,如果嘗試獲取鎖失敗了,則進(jìn)行acqurie方法的調(diào)用,例子中第一個(gè)線程當(dāng)嘗試獲取鎖是內(nèi)部state狀態(tài)為0,進(jìn)行修改操作的時(shí)候,發(fā)現(xiàn)鎖并沒有被占用,則獲得鎖,此時(shí)我們來看一下內(nèi)部變化的情況,如下圖所示:
此時(shí)只是將state的狀態(tài)更新為1,表示鎖已經(jīng)被占用了,獨(dú)占鎖資源的線程是Thread0,也就是exclusiveOwnerThread的內(nèi)容,頭節(jié)點(diǎn)和尾節(jié)點(diǎn)都沒有被初始化,當(dāng)?shù)诙€(gè)線程嘗試去獲取鎖的時(shí)候,發(fā)現(xiàn)鎖已經(jīng)被占用了,因?yàn)樯弦粋€(gè)線程并沒有釋放鎖,所以第二線程直接獲取鎖時(shí)獲取失敗則進(jìn)入到acquire方法中,這個(gè)方法是AbstractQueuedSynchronizer中的方法acquire,先來看一下具體的實(shí)現(xiàn)源碼如下所示:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
我個(gè)人理解acquire方法不間斷的嘗試獲取鎖,如果鎖沒有獲取到則現(xiàn)將節(jié)點(diǎn)加入到隊(duì)列中,并將當(dāng)前線程設(shè)置為獨(dú)占鎖資源,也就是獨(dú)占了鎖的意思,別的線程不能擁有鎖,然后如果當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)是頭節(jié)點(diǎn)話,再去嘗試爭搶鎖,則設(shè)置當(dāng)前節(jié)點(diǎn)為頭節(jié)點(diǎn),并將原頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為null,幫助GC回收它,如果不是頭節(jié)點(diǎn)或爭搶鎖不成功,則會(huì)現(xiàn)將前面節(jié)點(diǎn)的狀態(tài)設(shè)置直到設(shè)置為SIGNAL為止,代表下面有節(jié)點(diǎn)被等待了等待上一個(gè)線程發(fā)來的信號(hào),然后就掛起當(dāng)前線程。
我們接下來慢慢一步一步的分析,我們先來看一下NonfairSync中的tryAcquire,如下所示:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
它調(diào)用的是他的父類方法,也就是ReentrantLock下Sync中的nonfairTryAcquire方法,這個(gè)方法主要就是去申請(qǐng)鎖的操作,來看一下具體源碼:
final boolean nonfairTryAcquire(int acquires) { //首先是一個(gè)被final修飾的方法 final Thread current = Thread.currentThread(); //獲取當(dāng)前線程 int c = getState(); //獲取state的狀態(tài)值 if (c == 0) { //如果狀態(tài)等于0代表線程沒有被占用 if (compareAndSetState(0, acquires)) { //cas修改state值 setExclusiveOwnerThread(current); //設(shè)置當(dāng)前線程為獨(dú)占模式 return true; } } else if (current == getExclusiveOwnerThread()) {//如果state狀態(tài)不等于0則先判斷是否是當(dāng)前線程占用鎖,如果是則進(jìn)行下面的流程。 int nextc = c + acquires; //這個(gè)地方就說明重入鎖的原理,如果擁有鎖的是當(dāng)前線程,則每次獲取鎖state值都會(huì)跟隨遞增 if (nextc < 0) // overflow //溢出了 throw new Error("Maximum lock count exceeded"); setState(nextc); //直接設(shè)置state值就可以不需要CAS return true; } return false; //都不是就返回false }
通過源碼我們可以看到其實(shí)他是有三種操作邏輯:
如果state為0,則代表鎖沒有被占用,嘗試去修改state狀態(tài),并且將當(dāng)前線程設(shè)置為獨(dú)占鎖資源,表示獲得鎖成功
如果state大于0并且擁有鎖的線程和當(dāng)前申請(qǐng)鎖的線程一致,則代表重入了鎖,state值會(huì)進(jìn)行遞增,表示獲得鎖成功
如果state大于0并且擁有鎖的線程和當(dāng)前申請(qǐng)鎖的線程不一致則直接返回false,代表申請(qǐng)鎖失敗
當(dāng)?shù)诙€(gè)線程去爭搶鎖的時(shí)候,state值已經(jīng)設(shè)置為1了也就是已經(jīng)被第一個(gè)線程占用了鎖,所以這里它會(huì)返回false,而通過acquire方法內(nèi)容可以看到if語句中是!tryAcquire(arg),也就是!false=ture,它會(huì)進(jìn)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,這個(gè)方法里面又有一個(gè)addWaiter方法,從方法語義上能看到是添加等待隊(duì)列的操作,方法的參數(shù)代表的是模式,Node.EXCLUSIVE表示的是在獨(dú)占模式下等待,我們先來看一下addWaiter里面是如何進(jìn)行操作,如下所示:
private Node addWaiter(Node mode) { //首先生成當(dāng)前線程擁有的節(jié)點(diǎn) Node node = new Node(Thread.currentThread(), mode); // 下面的內(nèi)容是嘗試快速進(jìn)行插入末尾的操作,在沒有其他線程同時(shí)操作的情況 Node pred = tail; //獲取尾節(jié)點(diǎn) if (pred != null) { //尾節(jié)點(diǎn)不為空,代表隊(duì)列不為空 node.prev = pred; //尾節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn) if (compareAndSetTail(pred, node)) { //修改尾節(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn) pred.next = node; //原尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn) return node; //返回node節(jié)點(diǎn) } } enq(node); //如果前面入隊(duì)失敗,這里進(jìn)行循環(huán)入隊(duì)操作,直到入隊(duì)成功 return node; }
前面代碼中可以看到,它有一個(gè)快速入隊(duì)的操作,如果快速入隊(duì)失敗則進(jìn)行死循環(huán)進(jìn)行入隊(duì)操作,當(dāng)然我們上面例子中發(fā)現(xiàn)隊(duì)列其實(shí)是為空的,也就是pred==null,不能進(jìn)行快速入隊(duì)操作,則進(jìn)入到enq進(jìn)行入隊(duì)操作,下面看一下enq方法實(shí)現(xiàn),如下所示:
private Node enq(final Node node) { for (;;) { //死循環(huán)進(jìn)行入隊(duì)操作,直到入隊(duì)成功 Node t = tail; //獲取尾節(jié)點(diǎn) if (t == null) { // Must initialize //判斷尾節(jié)點(diǎn)為空,則必須先進(jìn)行初始化 if (compareAndSetHead(new Node())) //生成一個(gè)Node,并將當(dāng)前Node作為頭節(jié)點(diǎn) tail = head; //head和tail同時(shí)指向上面Node節(jié)點(diǎn) } else { node.prev = t; //設(shè)置入隊(duì)的當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)設(shè)置為尾節(jié)點(diǎn) if (compareAndSetTail(t, node)) { //將當(dāng)前節(jié)點(diǎn)設(shè)置為尾節(jié)點(diǎn) t.next = node; //修改原有尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn) return t; //返回最新的節(jié)點(diǎn) } } } }
通過上面入隊(duì)操作,可以清晰的了解入隊(duì)操作其實(shí)就是Node節(jié)點(diǎn)的prev節(jié)點(diǎn)和next節(jié)點(diǎn)之前的引用,運(yùn)行到這里我們應(yīng)該能看到入隊(duì)的狀態(tài)了,如下圖所示:
如上圖可以清晰的看到,此時(shí)擁有鎖的線程是Thread0,而當(dāng)前線程是Threa1,頭節(jié)點(diǎn)為初始化的節(jié)點(diǎn),Ref-707引用地址所在的Node節(jié)點(diǎn)操作當(dāng)前操作的節(jié)點(diǎn)信息,入隊(duì)操作后并沒有完成,而是繼續(xù)往下進(jìn)行,此時(shí)則進(jìn)行acquireQueued這個(gè)方法,這個(gè)方法是不間斷的去獲取已經(jīng)入隊(duì)隊(duì)列中的前節(jié)點(diǎn)的狀態(tài),如果前節(jié)點(diǎn)的狀態(tài)為大于0,則代表當(dāng)前節(jié)點(diǎn)被取消了,會(huì)一直往前面的節(jié)點(diǎn)進(jìn)行查找,如果節(jié)點(diǎn)狀態(tài)小于0并且不等于SIGNAL則將其設(shè)置為SIGNAL狀態(tài),設(shè)置成功后將當(dāng)前線程掛起,掛起線程后也有可能會(huì)反復(fù)喚醒掛起操作,原因后面會(huì)講到。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; //取消節(jié)點(diǎn)標(biāo)志位 try { boolean interrupted = false; //中斷標(biāo)志位 for (;;) { final Node p = node.predecessor(); //獲取前節(jié)點(diǎn) if (p == head && tryAcquire(arg)) { //這里的邏輯是如果前節(jié)點(diǎn)為頭結(jié)點(diǎn)并且獲取到鎖則進(jìn)行頭結(jié)點(diǎn)變換 setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && //設(shè)置waitStatus狀態(tài) parkAndCheckInterrupt()) //掛起線程 interrupted = true; } } finally { if (failed) cancelAcquire(node); //取消操作 } }
前面的源碼可以看到它在acquireQueued中對(duì)已經(jīng)入隊(duì)的節(jié)點(diǎn)進(jìn)行嘗試鎖的獲取,如果鎖獲得就修改頭節(jié)點(diǎn)的指針,如果不是頭節(jié)點(diǎn)或者爭搶鎖失敗時(shí),此時(shí)會(huì)進(jìn)入到shouldParkAfterFailedAcquire方法,這個(gè)方法是獲取不到鎖時(shí)需要停止繼續(xù)無限期等待鎖,其實(shí)就是內(nèi)部的操作邏輯也很簡單,就是如果前節(jié)點(diǎn)狀態(tài)為0時(shí),需要將前節(jié)點(diǎn)修改為SIGNAL,如果前節(jié)點(diǎn)大于0則代表前節(jié)點(diǎn)已經(jīng)被取消了,應(yīng)該移除隊(duì)列,并將前前節(jié)點(diǎn)作為當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn),一直循環(huán)直到前節(jié)點(diǎn)狀態(tài)修改為SIGNAL或者前節(jié)點(diǎn)被釋放鎖,當(dāng)前節(jié)點(diǎn)獲取到鎖停止循環(huán)。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * 此節(jié)點(diǎn)已經(jīng)設(shè)置了狀態(tài),要求對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行掛起操作 */ return true; if (ws > 0) { /* * 如果前節(jié)點(diǎn)被取消,則將取消節(jié)點(diǎn)移除隊(duì)列操作 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus=0或者PROPAGATE時(shí),表示當(dāng)前節(jié)點(diǎn)還沒有被掛起停止,需要等待信號(hào)來通知節(jié)點(diǎn)停止操作。 */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
上面的方法其實(shí)很容易理解就是等待掛起信號(hào),如果前節(jié)點(diǎn)的狀態(tài)為0或PROPAGATE則將前節(jié)點(diǎn)修改為SIGNAL,則代表后面前節(jié)點(diǎn)釋放鎖后會(huì)通知下一個(gè)節(jié)點(diǎn),也就是說喚醒下一個(gè)可以喚醒的節(jié)點(diǎn)繼續(xù)爭搶所資源,如果前節(jié)點(diǎn)被取消了那就繼續(xù)往前尋找不是被取消的節(jié)點(diǎn),這里不會(huì)找到前節(jié)點(diǎn)為null的情況,因?yàn)樗J(rèn)會(huì)有一個(gè)空的頭結(jié)點(diǎn),也就是上圖內(nèi)容,此時(shí)的隊(duì)列狀態(tài)是如何的我們看一下,這里它會(huì)進(jìn)來兩次,以為我們上圖可以看到當(dāng)前節(jié)點(diǎn)前節(jié)點(diǎn)是Ref-724此時(shí)waitStatus=0,他需要先將狀態(tài)更改為SIGNAL也就是運(yùn)行最有一個(gè)else語句,此時(shí)又會(huì)回到外面的for循環(huán)中,由于方法返回的是false則不會(huì)運(yùn)行parkAndCheckInterrupt方法,而是又循環(huán)了一次,此時(shí)發(fā)現(xiàn)當(dāng)前節(jié)點(diǎn)爭搶鎖又失敗了,然后此時(shí)隊(duì)列的狀態(tài)如下圖所示:
再次進(jìn)入到方法之后發(fā)現(xiàn)前驅(qū)節(jié)點(diǎn)的waitStatus=-1,表示當(dāng)前節(jié)點(diǎn)需要進(jìn)行掛起等到,此時(shí)返回的結(jié)果是true,則會(huì)運(yùn)行parkAndCheckInterrupt方法,這個(gè)方法很簡單就是將當(dāng)前線程進(jìn)行掛起操作,如下所示:
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); //掛起線程 return Thread.interrupted(); //判斷是否被中斷,獲取中斷標(biāo)識(shí) }
park掛起線程并且響應(yīng)中斷信息,其實(shí)我們從這里就能發(fā)現(xiàn)一個(gè)問題,Thread.interrupted方法是用來獲取是否被中斷的標(biāo)志,如果被中斷則返回true,如果沒有被中斷則返回false,當(dāng)當(dāng)前節(jié)點(diǎn)被中斷后,其實(shí)就會(huì)返回true,返回true這里并沒有結(jié)束,而是跳到調(diào)用地方,也就是acquireQueued方法內(nèi)部:
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true;
以一個(gè)案例來進(jìn)行分析:
public class ReentrantLockDemo { public static void main(String[] args) throws Exception { AddDemo runnalbeDemo = new AddDemo(); Thread thread = new Thread(runnalbeDemo::add); thread.start(); Thread thread1 = new Thread(runnalbeDemo::add); thread1.start(); Thread thread2 = new Thread(runnalbeDemo::add); thread2.start(); Thread.sleep(10000); thread1.interrupt(); System.out.println(runnalbeDemo.getCount()); } private static class AddDemo { private final AtomicInteger count = new AtomicInteger(); private final ReentrantLock reentrantLock = new ReentrantLock(); private final Condition condition = reentrantLock.newCondition(); private void add() { try { reentrantLock.lock(); count.getAndIncrement(); } finally { // reentrantLock.unlock(); } } int getCount() { return count.get(); } } }
通過上面的例子可以發(fā)現(xiàn),thread1調(diào)用中斷方法interrupt(),當(dāng)調(diào)用第一次方法的時(shí)候,它會(huì)進(jìn)入到parkAndCheckInterrupt方法,然后線程響應(yīng)中斷,最后返回true,最后返回到acquireQueued方法內(nèi)部,整個(gè)if語句為true,則開始設(shè)置interrupted=true,僅僅是設(shè)置了等于true,但是這離還會(huì)進(jìn)入下一輪的循環(huán),假如說上次的線程沒有完成任務(wù),則沒有獲取到鎖,還是會(huì)進(jìn)入到shouldParkAfterFailedAcquire由于已經(jīng)修改了上一個(gè)節(jié)點(diǎn)的waitStatus=-1,直接返回true,然后再進(jìn)入到parkAndCheckInterrupt又被掛起線程,但是如果上步驟操作他正搶到鎖,則會(huì)返回ture,外面也會(huì)清除中斷標(biāo)志位,從這里可以清楚地看到acquire方法是一個(gè)不間斷獲得鎖的操作,可能重復(fù)阻塞和解除阻塞操作。
上面阻塞隊(duì)列的內(nèi)容已經(jīng)講完了,接下來我們看一下unlock都為我們做了什么工作:
public void unlock() { sync.release(1); }
我們可以看到他直接調(diào)用了獨(dú)占模式的release方法,看一下具體源碼:
public final boolean release(int arg) { if (tryRelease(arg)) { //調(diào)用ReentrantLock中的Sync里面的tryRelease方法 Node h = head; //獲取頭節(jié)點(diǎn) if (h != null && h.waitStatus != 0) //頭節(jié)點(diǎn)不為空且狀態(tài)不為0時(shí)進(jìn)行unpark方法 unparkSuccessor(h); //喚醒下一個(gè)未被取消的節(jié)點(diǎn) return true; } return false; }
release方法,首先先進(jìn)行嘗試去釋放鎖,如果釋放鎖仍然被占用則直接返回false,如果嘗試釋放鎖時(shí),發(fā)現(xiàn)鎖已經(jīng)釋放,當(dāng)前線程不在占用鎖資源時(shí),則會(huì)進(jìn)入的下面進(jìn)行一些列操作后返回true,接下來我們先來看一下ReentrantLock的Sync下的tryRelease方法,如下所示:
protected final boolean tryRelease(int releases) { int c = getState() - releases; //獲取state狀態(tài),標(biāo)志信息減少1 if (Thread.currentThread() != getExclusiveOwnerThread()) //線程不一致拋出異常 throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { //是否已經(jīng)釋放鎖,start=0代表已經(jīng)釋放鎖 free = true; //將標(biāo)志free設(shè)置為true setExclusiveOwnerThread(null); //取消獨(dú)占鎖信息 } setState(c); //設(shè)置鎖標(biāo)志信息 return free; }
看上面的源碼,表示首先先獲取state狀態(tài),如果state狀態(tài)減少1之后和0不相等則代表有重入鎖,則表示當(dāng)前線程還在占用所資源,直到線程釋放鎖返回ture標(biāo)識(shí),還是以上例子為主(此時(shí)AddDemo中的unlock不在被注釋),分析其現(xiàn)在的隊(duì)列中的狀態(tài)
釋放鎖后,進(jìn)入到if語句中,判斷當(dāng)前頭節(jié)點(diǎn)不為空且waitStatus!=0,通過上圖也可以發(fā)現(xiàn)頭節(jié)點(diǎn)為-1,則進(jìn)入到unparkSuccessor方法內(nèi):
private void unparkSuccessor(Node node) { /* * 獲取節(jié)點(diǎn)的waitStatus狀態(tài) */ int ws = node.waitStatus; // 如果小于0則設(shè)置為0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * 喚醒下一個(gè)節(jié)點(diǎn),喚醒下一個(gè)節(jié)點(diǎn)之前需要判斷節(jié)點(diǎn)是否存在或已經(jīng)被取消了節(jié)點(diǎn),如果沒有節(jié)點(diǎn)則不需喚醒操作,如果下一個(gè)節(jié)點(diǎn)被取消了則一直一個(gè)沒有被取消的節(jié)點(diǎn)。 */ 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) LockSupport.unpark(s.thread); }
可以看到它是現(xiàn)將頭節(jié)點(diǎn)的狀態(tài)更新為0,然后再喚醒下一個(gè)節(jié)點(diǎn),如果下一個(gè)節(jié)點(diǎn)為空則直接返回不喚醒任何節(jié)點(diǎn),如果下一個(gè)節(jié)點(diǎn)被取消了,那么它會(huì)從尾節(jié)點(diǎn)往前進(jìn)行遍歷,遍歷與頭節(jié)點(diǎn)最近的沒有被取消的節(jié)點(diǎn)進(jìn)行喚醒操作,在喚醒前看一下隊(duì)列狀態(tài):
然后喚醒節(jié)點(diǎn)后他會(huì)進(jìn)入到parkAndCheckInterrupt方法里面,再次去執(zhí)行下面的方法:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; //取消節(jié)點(diǎn)標(biāo)志位 try { boolean interrupted = false; //中斷標(biāo)志位 for (;;) { final Node p = node.predecessor(); //獲取前節(jié)點(diǎn) if (p == head && tryAcquire(arg)) { //這里的邏輯是如果前節(jié)點(diǎn)為頭結(jié)點(diǎn)并且獲取到鎖則進(jìn)行頭結(jié)點(diǎn)變換 setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && //設(shè)置waitStatus狀態(tài) parkAndCheckInterrupt()) //掛起線程 interrupted = true; } } finally { if (failed) cancelAcquire(node); //取消操作 } }
此時(shí)獲取p==head成立,并且可以正搶到所資源,所以它會(huì)進(jìn)入到循環(huán)體內(nèi),進(jìn)行設(shè)置頭結(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn),前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為null,返回中斷標(biāo)志,看一下此時(shí)隊(duì)列情況,如下圖所示:
AbstractQueuedSynchronizer的獨(dú)占模式其實(shí)提供了三種不同的形式進(jìn)行獲取鎖操作,看一下下表所示:
方法名稱 | 方法描述 | 對(duì)應(yīng)調(diào)用的內(nèi)部方法 |
---|---|---|
acquire | 以獨(dú)占模式進(jìn)行不間斷的獲取鎖 | tryAcquire,acquireQueued |
acquireInterruptibly | 以獨(dú)占模式相應(yīng)中斷的方式獲取鎖,發(fā)生中斷拋出異常 | tryAcquire,doAcquireInterruptibly |
tryAcquireNanos | 以獨(dú)占模式相應(yīng)中斷的方式并且在指定時(shí)間內(nèi)獲取鎖,會(huì)阻塞一段時(shí)間,如果還未獲得鎖直接返回,發(fā)生中斷拋出異常 | tryAcquire,doAcquireNanos |
通過上面圖可以發(fā)現(xiàn),他都會(huì)調(diào)用圖表一中需要用戶實(shí)現(xiàn)的方法,ReentrantLock實(shí)現(xiàn)了獨(dú)占模式則內(nèi)部實(shí)現(xiàn)的是tryAcquire和tryRelease方法,用來嘗試獲取鎖和嘗試釋放鎖的操作,其實(shí)上面內(nèi)容我們用的是ReentrantLock中的lock方法作為同步器,細(xì)心的朋友會(huì)發(fā)現(xiàn),這個(gè)lock,方法是ReentrantLock實(shí)現(xiàn)的,它內(nèi)部調(diào)用了acquire方法,實(shí)現(xiàn)了不間斷的獲取鎖機(jī)制,ReentrantLock中還有一個(gè)lockInterruptibly方法,它內(nèi)部直接調(diào)用的是AbstractQueuedSynchronizer的acquireInterruptibly方法,兩個(gè)之間的區(qū)別在于,兩者都會(huì)相應(yīng)中斷信息,前者不會(huì)做任何處理還會(huì)進(jìn)入等待狀態(tài),而后者則拋出異常終止操作,
這里為了詳細(xì)看清楚它內(nèi)部關(guān)系我這里用張圖來進(jìn)行闡述,如下所示:
左側(cè)代表的事ReentrantLock,右側(cè)代表的AQS
左側(cè)內(nèi)部黃色區(qū)域代表NonfairSync
圖中1和2代表AQS調(diào)用其他方法的過程
接下來我們來看一下源碼信息:
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
發(fā)現(xiàn)他調(diào)用的Sync類中的acquireInterruptibly方法,但其實(shí)這個(gè)方法是AQS中的方法,源碼如下所示:
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) //判斷線程是否被中斷 throw new InterruptedException(); //中斷則拋出異常 if (!tryAcquire(arg)) //嘗試獲取鎖 doAcquireInterruptibly(arg); //進(jìn)行添加隊(duì)列,并且修改前置節(jié)點(diǎn)狀態(tài),且響應(yīng)中斷拋出異常 }
通過上面的源碼,它也調(diào)用了子類實(shí)現(xiàn)的tryAcquire方法,這個(gè)方法和我們上文提到的tryAcquire是一樣,ReentrantLock下的NonfairSync下的tryAcquire方法,這里這個(gè)方法就不多說了詳細(xì)請(qǐng)看上文內(nèi)容,這里主要講一下doAcquireInterruptibly這個(gè)方法:
private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); //將節(jié)點(diǎn)添加到隊(duì)列尾部 boolean failed = true; //失敗匹配機(jī)制 try { for (;;) { final Node p = node.predecessor(); //獲取前節(jié)點(diǎn) if (p == head && tryAcquire(arg)) { //如果前節(jié)點(diǎn)為頭節(jié)點(diǎn)并且獲得了鎖 setHead(node); //設(shè)置當(dāng)前節(jié)點(diǎn)為頭節(jié)點(diǎn) p.next = null; // help GC //頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為null failed = false; //匹配失敗變?yōu)閒alse return; } if (shouldParkAfterFailedAcquire(p, node) && //將前節(jié)點(diǎn)設(shè)置為-1,如果前節(jié)點(diǎn)為取消節(jié)點(diǎn)則往前一直尋找直到修改為-1為止。 parkAndCheckInterrupt()) //掛起線程返回是否中斷 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
其實(shí)這個(gè)方法和acquireQueued區(qū)別在于以下幾點(diǎn):
acquireQueued是在方法內(nèi)部添加節(jié)點(diǎn)到隊(duì)列尾部,而doAcquireInterruptibly是在方法內(nèi)部進(jìn)行添加節(jié)點(diǎn)到尾部,這個(gè)區(qū)別點(diǎn)并不是很重要
重點(diǎn)是acquireQueued響應(yīng)中斷,但是他不會(huì)拋出異常,而后者會(huì)拋出異常throw new InterruptedException()
分析到這里我們來用前面的例子來進(jìn)行模擬一下中中斷的操作,詳細(xì)代碼如下所示:
public class ReentrantLockDemo { public static void main(String[] args) throws Exception { AddDemo runnalbeDemo = new AddDemo(); Thread thread = new Thread(runnalbeDemo::add); thread.start(); Thread.sleep(500); Thread thread1 = new Thread(runnalbeDemo::add); thread1.start(); Thread.sleep(500); Thread thread2 = new Thread(runnalbeDemo::add); thread2.start(); Thread.sleep(500); Thread thread3 = new Thread(runnalbeDemo::add); thread3.start(); Thread.sleep(10000); thread1.interrupt(); System.out.println(runnalbeDemo.getCount()); } private static class AddDemo { private final AtomicInteger count = new AtomicInteger(); private final ReentrantLock reentrantLock = new ReentrantLock(); private final Condition condition = reentrantLock.newCondition(); private void add() { try { reentrantLock.lockInterruptibly(); count.getAndIncrement(); } catch (Exception ex) { System.out.println("線程被中斷了"); } finally { // reentrantLock.unlock(); } } int getCount() { return count.get(); } } }
上面的例子其實(shí)和前面提到的例子沒有什么太大的差別主要的差別是將lock替換為lockInterruptibly,其次就是在三個(gè)線程后面講線程1進(jìn)行中斷操作,這里入隊(duì)的操作不在多說,因?yàn)椴僮鲀?nèi)容和上面大致相同,下面是四個(gè)個(gè)線程操作完成的狀態(tài)信息:
如果線程等待的過程中拋出異常,則當(dāng)前線程進(jìn)入到finally中的時(shí)候failed為true,因?yàn)樾薷脑撟侄沃挥蝎@取到鎖的時(shí)候才會(huì)修改為false,進(jìn)來之后它會(huì)運(yùn)行cancelAcquire來進(jìn)行取消當(dāng)前節(jié)點(diǎn),下面我們先來分析下源碼內(nèi)容:
private void cancelAcquire(Node node) { // 如果節(jié)點(diǎn)為空直接返回,節(jié)點(diǎn)不存在直接返回 if (node == null) return; // 設(shè)置節(jié)點(diǎn)所在的線程為空,清除線程操作 node.thread = null; // 獲取當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn) Node pred = node.prev; // 如果前節(jié)點(diǎn)是取消節(jié)點(diǎn)則跳過前節(jié)點(diǎn),一直尋找一個(gè)不是取消節(jié)點(diǎn)為止 while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 獲取頭節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn) Node predNext = pred.next; // 這里直接設(shè)置為取消節(jié)點(diǎn)狀態(tài),沒有使用CAS原因是因?yàn)橹苯釉O(shè)置只有其他線程可以跳過取消的節(jié)點(diǎn) node.waitStatus = Node.CANCELLED; // 如果當(dāng)前節(jié)點(diǎn)為尾節(jié)點(diǎn),并且設(shè)置尾節(jié)點(diǎn)為找到的合適的前節(jié)點(diǎn)時(shí),修改前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)為null if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // 如果不是尾節(jié)點(diǎn),則說明是中間節(jié)點(diǎn),則需要通知后續(xù)節(jié)點(diǎn),嘿,伙計(jì)你被喚醒了。 int ws; if (pred != head && //前節(jié)點(diǎn)不是頭結(jié)點(diǎn) ((ws = pred.waitStatus) == Node.SIGNAL || // 前節(jié)點(diǎn)的狀態(tài)為SIGNAL (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) //或者前節(jié)點(diǎn)狀態(tài)小于0而且修改前節(jié)點(diǎn)狀態(tài)為SIGNAL成功 && pred.thread != null) { //前節(jié)點(diǎn)線程不為空 Node next = node.next; if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { //喚醒下一個(gè)不是取消的節(jié)點(diǎn) unparkSuccessor(node); } node.next = node; // help GC } }
首先找到當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn),如果前節(jié)點(diǎn)為取消節(jié)點(diǎn)則一直往前尋找一個(gè)節(jié)點(diǎn)。
取消的是尾節(jié)點(diǎn),則直接將前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為null
如果取消的是頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),且不是尾節(jié)點(diǎn)的情況時(shí),它是喚醒下一個(gè)節(jié)點(diǎn),喚醒之前并沒有將其移除隊(duì)列,而是在喚醒下一個(gè)節(jié)點(diǎn)的時(shí)候,shouldParkAfterFailedAcquire里面將取消的節(jié)點(diǎn)移除隊(duì)列,喚醒之后,當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)也設(shè)置成自己,幫助GC回收它。
如果取消節(jié)點(diǎn)是中間的節(jié)點(diǎn),則直接將其前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為取消節(jié)點(diǎn)的下下個(gè)節(jié)點(diǎn)即可。
第一種情況如果我們?nèi)∠墓?jié)點(diǎn)是前節(jié)點(diǎn)是頭節(jié)點(diǎn),此時(shí)線程1的節(jié)點(diǎn)應(yīng)該是被中斷操作,此時(shí)進(jìn)入到cancelAcquire之后會(huì)進(jìn)入else語句中,然后進(jìn)去到unparkSuccessor方法,當(dāng)進(jìn)入到這個(gè)方法之前我們看一下狀態(tài)變化:
我們發(fā)現(xiàn)線程1的Node節(jié)點(diǎn)的waitStatus變?yōu)?也就是Node.CANCELLED節(jié)點(diǎn),然后運(yùn)行unparkSuccessor方法,該方法上面就已經(jīng)講述了其中的源碼,這里就不在貼源碼了,就是要喚醒下一個(gè)沒有被取消的節(jié)點(diǎn),這里是Ref-695這個(gè)線程,當(dāng)Ref-695被喚醒之后它會(huì)繼續(xù)運(yùn)行下面的內(nèi)容:
private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { //再一次循環(huán)發(fā)現(xiàn)還是沒有爭搶到鎖 setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && //再一次循環(huán)之后有運(yùn)行到這里了 parkAndCheckInterrupt()) //這里被喚醒了,又要進(jìn)行循環(huán)操作了 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
發(fā)現(xiàn)再一次循環(huán)操作后,還是沒有正搶到鎖,這時(shí)候還是會(huì)運(yùn)行shouldParkAfterFailedAcquire方法,這個(gè)方法內(nèi)部發(fā)現(xiàn)前節(jié)點(diǎn)的狀態(tài)是Node.CANCELLED這時(shí)候它會(huì)在內(nèi)部先將節(jié)點(diǎn)給干掉,也就是這個(gè)代碼:
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; }
最后還是會(huì)被掛起狀態(tài),因?yàn)闆]有釋放鎖操作,最后移除的節(jié)點(diǎn)如下所示:
如果取消的事尾節(jié)點(diǎn),也就是線程3被中斷操作,這個(gè)是比較簡單的直接將尾節(jié)點(diǎn)刪除即可,其中會(huì)走如下代碼:
if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); }
如果取消的節(jié)點(diǎn)是中間的節(jié)點(diǎn),通過上例子中則是取消線程2,其實(shí)它內(nèi)部只是將線程取消線程的前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)指向了取消節(jié)點(diǎn)的下節(jié)點(diǎn),如下圖所示:
結(jié)束語這章節(jié)分析的主要是ReentrantLock的內(nèi)部原理,本來公平模式和非公平模式想放在一起來寫,無奈發(fā)現(xiàn)篇幅有點(diǎn)長了,所以就分開進(jìn)行寫,這樣讀取來不會(huì)那么費(fèi)勁,內(nèi)部還有條件內(nèi)容等待下章節(jié)分析,如果有分析不到位的請(qǐng)大家指正。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74758.html
摘要:概述前面已經(jīng)講解了關(guān)于的非公平鎖模式,關(guān)于非公平鎖,內(nèi)部其實(shí)告訴我們誰先爭搶到鎖誰就先獲得資源,下面就來分析一下公平鎖內(nèi)部是如何實(shí)現(xiàn)公平的如果沒有看過非公平鎖的先去了解下非公平鎖,因?yàn)檫@篇文章前面不會(huì)講太多內(nèi)部結(jié)構(gòu),直接會(huì)對(duì)源碼進(jìn)行分析前文 概述 前面已經(jīng)講解了關(guān)于AQS的非公平鎖模式,關(guān)于NonfairSync非公平鎖,內(nèi)部其實(shí)告訴我們誰先爭搶到鎖誰就先獲得資源,下面就來分析一下公平...
摘要:所以大家看下面的圖,就是線程跑過來加鎖的一個(gè)過程。好線程現(xiàn)在就重新嘗試加鎖,這時(shí)還是用操作將從變?yōu)椋藭r(shí)就會(huì)成功,成功之后代表加鎖成功,就會(huì)將設(shè)置為。此外,還要把加鎖線程設(shè)置為線程自己,同時(shí)線程自己就從等待隊(duì)列中出隊(duì)了。 ReentrantLock和AQS的關(guān)系 ReentrantLock使用 showImg(https://segmentfault.com/img/bVbkZr4?...
摘要:的主要功能和關(guān)鍵字一致,均是用于多線程的同步。而僅支持通過查詢當(dāng)前線程是否持有鎖。由于和使用的是同一把可重入鎖,所以線程可以進(jìn)入方法,并再次獲得鎖,而不會(huì)被阻塞住。公平與非公平公平與非公平指的是線程獲取鎖的方式。 1.簡介 可重入鎖ReentrantLock自 JDK 1.5 被引入,功能上與synchronized關(guān)鍵字類似。所謂的可重入是指,線程可對(duì)同一把鎖進(jìn)行重復(fù)加鎖,而不會(huì)被阻...
摘要:開始獲取鎖終于輪到出場了,的調(diào)用過程和完全一樣,同樣拿不到鎖,然后加入到等待隊(duì)列隊(duì)尾然后,在阻塞前需要把前驅(qū)結(jié)點(diǎn)的狀態(tài)置為,以確保將來可以被喚醒至此,的執(zhí)行也暫告一段落了安心得在等待隊(duì)列中睡覺。 showImg(https://segmentfault.com/img/remote/1460000016012467); 本文首發(fā)于一世流云的專欄:https://segmentfault...
摘要:公平策略在多個(gè)線程爭用鎖的情況下,公平策略傾向于將訪問權(quán)授予等待時(shí)間最長的線程。使用方式的典型調(diào)用方式如下二類原理的源碼非常簡單,它通過內(nèi)部類實(shí)現(xiàn)了框架,接口的實(shí)現(xiàn)僅僅是對(duì)的的簡單封裝,參見原理多線程進(jìn)階七鎖框架獨(dú)占功能剖析 showImg(https://segmentfault.com/img/remote/1460000016012582); 本文首發(fā)于一世流云的專欄:https...
閱讀 2204·2021-11-15 11:38
閱讀 1164·2021-09-06 15:02
閱讀 3404·2021-08-27 13:12
閱讀 1372·2019-08-30 14:20
閱讀 2410·2019-08-29 15:08
閱讀 651·2019-08-29 14:08
閱讀 1736·2019-08-29 13:43
閱讀 1472·2019-08-26 12:11