淺談JAVA并發(fā)之ReentrantLock
結(jié)合上面的ReentrantLock類(lèi)圖,ReentrantLock實(shí)現(xiàn)了Lock接口,它的內(nèi)部類(lèi)Sync繼承自AQS,絕大部分使用AQS的子類(lèi)需要自定義的方法存在Sync中。而ReentrantLock有公平與非公平的區(qū)別,即’是否先阻塞就先獲取資源’,它的主要實(shí)現(xiàn)就是FairSync與NonfairSync,后面會(huì)從源碼角度看看它們的區(qū)別。
2. 源碼剖析Sync是ReentrantLock控制同步的基礎(chǔ)。它的子類(lèi)分為了公平與非公平。使用AQS的state代表獲取鎖的數(shù)量
abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /*** Performs {@link Lock#lock}. The main reason for subclassing* is to allow fast path for nonfair version.*/ abstract void lock(); ...}
我們可以看出內(nèi)部類(lèi)Sync是一個(gè)抽象類(lèi),繼承它的子類(lèi)(FairSync與NonfairSync)需要實(shí)現(xiàn)抽象方法lock。
下面我們先從非公平鎖的角度來(lái)看看獲取資源與釋放資源的原理
故事就從就兩個(gè)變量開(kāi)始:
// 獲取一個(gè)非公平的獨(dú)占鎖/*** public ReentrantLock() {* sync = new ReentrantLock.NonfairSync();* }*/private Lock lock = new ReentrantLock();// 獲取條件變量private Condition condition = lock.newCondition();2.1 上鎖(獲取資源)
lock.lock()
public void lock() { sync.lock();}
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; // 獲取資源 final void lock() {// 若此時(shí)沒(méi)有線(xiàn)程獲取到資源,直接設(shè)置當(dāng)前線(xiàn)程獨(dú)占訪(fǎng)問(wèn)資源。if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread());else // AQS的方法 acquire(1); } protected final boolean tryAcquire(int acquires) {// 實(shí)現(xiàn)在父類(lèi)Sync中return nonfairTryAcquire(acquires); }}
AQS的acquire
public final void acquire(int arg) { if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
// Sync實(shí)現(xiàn)的非公平的tryAcquirefinal boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 此時(shí)若沒(méi)有線(xiàn)程獲取到資源,當(dāng)前線(xiàn)程就直接占用該資源 if (c == 0) {if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true;} } // 若當(dāng)前線(xiàn)程已經(jīng)占用了該資源,可以再次獲取該資源 ->這個(gè)行為就是可重入鎖的支撐 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;}
嘗試獲取資源的過(guò)程是非常簡(jiǎn)單的,這里再貼一下acquire的流程
lock.unlock();
public void unlock() { // AQS的方法 sync.release(1);}
AQS的release
public final boolean release(int arg) { if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0) unparkSuccessor(h);return true; } return false;}
release的流程已經(jīng)剖析過(guò)了,接下來(lái)看看tryRelease的實(shí)現(xiàn)
protected final boolean tryRelease(int releases) { int c = getState() - releases; // 這里可以看出若沒(méi)有持有鎖,就釋放資源,就會(huì)報(bào)錯(cuò) if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {free = true;setExclusiveOwnerThread(null); } setState(c); return free;}
tryRelease的實(shí)現(xiàn)也很簡(jiǎn)單,這里再貼一下release的流程圖
公平鎖與非公平鎖,即’是否先阻塞就先獲取資源’, ReentrantLock中公平與否的控制就在tryAcquire中。下面我們看看,公平鎖的tryAcquire
static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() { acquire(1);}protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {// (2.3.1)// sync queue中是否存在前驅(qū)結(jié)點(diǎn)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;} }
區(qū)別在代碼(2.3.1)
hasQueuedPredecessors
判斷當(dāng)前線(xiàn)程的前面有無(wú)其他線(xiàn)程排隊(duì);若當(dāng)前線(xiàn)程在隊(duì)列頭部或者隊(duì)列為空返回false
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}
結(jié)合下面的入隊(duì)代碼(enq), 我們分析hasQueuedPredecessors為true的情況:
1.h != t ,表示此時(shí)queue不為空; (s = h.next) == null, 表示另一個(gè)結(jié)點(diǎn)已經(jīng)運(yùn)行了下面的步驟(2),還沒(méi)來(lái)得及運(yùn)行步驟(3)。簡(jiǎn)言之,就是B線(xiàn)程想要獲取鎖的同時(shí),A線(xiàn)程獲取鎖失敗剛好在入隊(duì)(B入隊(duì)的同時(shí),之前占有的資源的線(xiàn)程,剛好釋放資源)
2.h != t 且 (s = h.next) != null,表示此時(shí)至少有一個(gè)結(jié)點(diǎn)在sync queue中;s.thread != Thread.currentThread(),這個(gè)情況比較復(fù)雜,設(shè)想一下有這三個(gè)結(jié)點(diǎn) A -> B C, A此時(shí)獲取到資源,而B(niǎo)此時(shí)因?yàn)楂@取資源失敗正在sync queue阻塞,C還沒(méi)有獲取資源(還沒(méi)有執(zhí)行tryAcquire)。
時(shí)刻一:A釋放資源成功后(執(zhí)行tryRelease成功),B此時(shí)還沒(méi)有成功獲取資源(C執(zhí)行s = h.next時(shí),B還在sync queue中且是老二)
時(shí)刻二: C此時(shí)執(zhí)行hasQueuedPredecessors,s.thread != Thread.currentThread()成立,此時(shí)s.thread表示的是B
private Node enq(final Node node) { for (;;) {Node t = tail;if (t == null) { // Must initialize if (compareAndSetHead(new Node())) // (1) 第一次初始化tail = head;} else { node.prev = t; if (compareAndSetTail(t, node)) { // (2) 設(shè)置queue的tailt.next = node; // (3)return t; }} }}
Note that 1. because cancellations due to interrupts and timeouts may occur at any time, a true return does not guarantee that some other thread will acquire before the current thread(虛假true). 2. Likewise, it is possible for another thread to win a race to enqueue after this method has returned false, due to the queue being empty(虛假false).
這位大佬對(duì)hasQueuedPredecessors進(jìn)行詳細(xì)的分析,他文中解釋了虛假true以及虛假false。我這里簡(jiǎn)單解釋一下:
1.虛假true, 當(dāng)兩個(gè)線(xiàn)程都執(zhí)行tryAcquire,都執(zhí)行到hasQueuedPredecessors,都返回true,但是只有一個(gè)線(xiàn)程執(zhí)行compareAndSetState(0, acquires)成功
2.虛假false,當(dāng)一個(gè)線(xiàn)程A執(zhí)行doAcquireInterruptibly,發(fā)生了中斷,還沒(méi)有清除掉該結(jié)點(diǎn)時(shí);此時(shí),線(xiàn)程B執(zhí)行hasQueuedPredecessors時(shí),返回true
以上就是淺談JAVA并發(fā)之ReentrantLock的詳細(xì)內(nèi)容,更多關(guān)于JAVA并發(fā)之ReentrantLock的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. python如何實(shí)現(xiàn)word批量轉(zhuǎn)HTML2. python excel和yaml文件的讀取封裝3. 利用單元測(cè)試對(duì)PHP代碼進(jìn)行檢查4. python3實(shí)現(xiàn)往mysql中插入datetime類(lèi)型的數(shù)據(jù)5. Java8內(nèi)存模型PermGen Metaspace實(shí)例解析6. python爬蟲(chóng)實(shí)戰(zhàn)之制作屬于自己的一個(gè)IP代理模塊7. moment轉(zhuǎn)化時(shí)間戳出現(xiàn)Invalid Date的問(wèn)題及解決8. 如何對(duì)php程序中的常見(jiàn)漏洞進(jìn)行攻擊9. python 實(shí)現(xiàn)圍棋游戲(純tkinter gui)10. Python實(shí)現(xiàn)http接口自動(dòng)化測(cè)試的示例代碼
