ReentrantLock重入鎖和 AQS同步器源碼解析
- AQS就是AbstractQueuedSynchronizer撵割,是一個(gè)java的同步器,用來管理多線程對(duì)共享資源的爭搶包颁,以及對(duì)線程的排隊(duì)和喚醒瞻想。
- 同步器支持獨(dú)占式的獲取資源也支持共享式的獲取資源
- 獨(dú)占式資源需要實(shí)現(xiàn)tryAcquire和tryRelease方法
- 共享式資源需要實(shí)現(xiàn)tryAcquireShared和tryReleaseShared
- 同步器內(nèi)部維護(hù)了一個(gè)雙向鏈表,用來保存爭搶鎖的隊(duì)列
- 同步器主要利用cas實(shí)現(xiàn)線程安全
- java api的ReentrantLock(可重入鎖)和CountDownLatch都利用了同步器來實(shí)現(xiàn)
- ReentrantLock是利用獨(dú)占式資源實(shí)現(xiàn)
- CountDownLatch是利用共享式資源實(shí)現(xiàn)
ReentrantLock 可重入鎖分析
可重入鎖分為公平鎖和非公平鎖
公平鎖是所有爭搶鎖的線程會(huì)形成一個(gè)隊(duì)列娩嚼,以先來先得順序獲取鎖
非公平鎖是每個(gè)新到的線程都會(huì)去嘗試獲取一次鎖蘑险,獲取不到才會(huì)排到隊(duì)列里
-
非公平鎖NonfairSync分析
Sync繼承自AQS,NonfairSync 繼承自 Sync
NonfairSync實(shí)現(xiàn)了lock方法以及 tryAcquire方法,同時(shí)Sync里有一個(gè)nonfairTryAcquire方法用于非公平鎖獲取鎖
接下來開始看非公平鎖的源碼解析岳悟,首先是lock方法
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // lock方法是加鎖方法佃迄,該方法會(huì)首先用cas嘗試獲得一次鎖,如果成功設(shè)置獲得鎖線程會(huì)當(dāng)前線程 // 否則執(zhí)行acquire加鎖方法贵少,該方法是AQS的方法
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 該方法首先執(zhí)行左邊tryAcquire嘗試獲得鎖呵俏,該方法由子類NonfairSync實(shí)現(xiàn), // 如果獲得鎖則&&后面不執(zhí)行滔灶,如果不能獲得鎖普碎,則執(zhí)行acquireQueued方法將鎖加入隊(duì)列。 // 而tryAcquire方法在子類實(shí)際是調(diào)用Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // 該方法會(huì)獲取當(dāng)前資源狀態(tài)录平,state麻车,0代表無人持有鎖,大于0則代表由線程持有鎖 // 如果是0斗这,則嘗試cas獲取动猬,如果當(dāng)前獲取鎖的線程是當(dāng)前線程,則將鎖狀態(tài)加1涝影,代表重入一次 // 否則獲取不到鎖返回false
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } // 該方法會(huì)將當(dāng)前獲取鎖的線程封裝成Node鏈入鏈表尾部枣察, // 如果當(dāng)前鏈表是空的話,會(huì)執(zhí)行enq初始化一個(gè)鏈表
private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } // 這里首先會(huì)開啟一個(gè)循環(huán)自旋燃逻,然后判斷鏈表是否是空序目, // 如果是空會(huì)初始化一個(gè)空節(jié)點(diǎn)作為鏈表的頭,然后在將當(dāng)前節(jié)點(diǎn)鏈入節(jié)點(diǎn)的尾部 // 那么為什么要把當(dāng)前節(jié)點(diǎn)鏈表鏈入空節(jié)點(diǎn)后伯襟,而不是head節(jié)點(diǎn)呢猿涨, // 這是因?yàn)閔ead節(jié)點(diǎn)始終是作為當(dāng)前持有線程的節(jié)點(diǎn), // 但是當(dāng)前持有線程的節(jié)點(diǎn)可能會(huì)因?yàn)橹苯訝帗尩芥i而沒有進(jìn)入鏈表姆怪,比如當(dāng)前這個(gè)情況叛赚, // 該線程沒獲得鎖,但是鏈表空的稽揭,所以要把他自己放在第二個(gè)位置
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } // 節(jié)點(diǎn)鏈入鏈表后俺附,會(huì)開啟自旋為自己獲取鎖, // 首先會(huì)判斷當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)是不是head節(jié)點(diǎn)溪掀, // 如果是說明當(dāng)前節(jié)點(diǎn)可以嘗試獲取一次鎖事镣,如果獲取到則返回, // 獲取不到則會(huì)shouldParkAfterFailedAcquire揪胃,將前置節(jié)點(diǎn)設(shè)置為Signal璃哟, // 告訴他釋放鎖后進(jìn)行通知氛琢,然后會(huì)執(zhí)行parkAndCheckInterrupt, // 該方法內(nèi)部會(huì)執(zhí)行LockSupport.park(this);随闪,該行代碼會(huì)將當(dāng)前線程阻塞阳似,直到被unPark喚醒 // 當(dāng)然 可能線程可能被interrupted而結(jié)束
到這里加鎖方法就結(jié)束了,接下來分析一下鎖的釋放铐伴,釋放鎖的方法
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } // 釋放鎖的方法是由重入鎖的unLock方法調(diào)用AQS的release方法實(shí)現(xiàn) // 該方法首先會(huì)執(zhí)行tryRelease 進(jìn)行鎖的釋放撮奏,該方法是由Sync實(shí)現(xiàn), // 釋放后會(huì)判斷鏈表節(jié)點(diǎn)是否是空当宴,并且首節(jié)點(diǎn)是否不等于0(Signal狀態(tài)是-1)挽荡, // 然后執(zhí)行喚醒下個(gè)線程的操作unparkSuccessor
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } // 先看Sync的tryRelease方法,先判斷當(dāng)前線程是否是獲取到鎖的線程即供,如果不是則拋出異常 // 接下來當(dāng)鎖狀態(tài)減1(參數(shù)就是1)后是否等于0,等于0代表鎖已經(jīng)完全釋放于微,可以返回true逗嫡,否則返回false // 只有返回true AQS才會(huì)去處理喚等待線程
private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); 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); } // 在這里,如果狀態(tài)Signal(小于0)代表是需要通知株依,然后首先將首節(jié)點(diǎn)設(shè)置為0 // 然后獲取到下一個(gè)節(jié)點(diǎn)驱证,如果下一個(gè)節(jié)點(diǎn)是null或者下一個(gè)節(jié)點(diǎn)的狀態(tài)是廢棄(狀態(tài)大于0代表是廢棄的節(jié)點(diǎn)), // 則從尾部開始逐漸向前著恋腕,直到找到一個(gè)可用節(jié)點(diǎn)(小于等于0的節(jié)點(diǎn)), // 這里為什么要從尾部向前找呢抹锄?我也不知道。荠藤。伙单。。 // 執(zhí)行unPark喚醒找到的節(jié)點(diǎn)對(duì)應(yīng)的線程
釋放鎖的流程也就這么結(jié)束了哈肖,這就是非公平鎖的流程吻育,那么公平鎖的流程是什么樣的呢?剛才我們提到了淤井,非公平鎖的不公平點(diǎn)在于每個(gè)新來爭搶的線程都會(huì)去嘗試獲取一次鎖布疼,也就是插隊(duì)加塞一次,獲取不到才會(huì)去乖乖排隊(duì)币狠,那么公平鎖在獲取鎖的時(shí)候一定就是要判斷有沒有人在等待游两,有的話就去排隊(duì),沒有才去獲取鎖漩绵,接下來看看源碼
在FairSync下的tryAcquire方法
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } // 所以我們看到贱案,線程在鎖資源沒有人持有的情況下,會(huì)首先執(zhí)行hasQueuedPredecessors判斷是否有線程在等待渐行。 // 如果沒人等待才會(huì)執(zhí)行cas獲取鎖轰坊。 // 而如果持有鎖的是當(dāng)前線程會(huì)將鎖資源加1铸董,這和非公平鎖一致。 //最后如果因?yàn)橛腥说却龥]獲取到鎖肴沫,AQS的acquire方法會(huì)執(zhí)行acquireQueued方法將節(jié)點(diǎn)鏈入鏈表 // 而釋放鎖對(duì)于非公平鎖和公平鎖來說都是一致的粟害。 // 這就是非公平鎖和公平鎖不一致的地方