您好,登錄后才能下訂單哦!
本篇內容介紹了“ReentrantLock源碼分析”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
從類圖我們可以直觀地了解到,ReentrantLock最終還是使用AQS來實現地,并且根據參數來決定其內部是一個公平????還是非公平鎖????,默認是非公平鎖????。
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
其中Sync類直接繼承自AQS,它的子類NonfairSync和FairSync分別實現了獲取鎖的非公平與公平策略。
如果讀者對AQS還不了解的話,可以去看看我的這篇文章:抽象同步隊列AQS——AbstractQueuedSynchronizer鎖詳解
在這里,AQS的state狀態值表示線程獲取該鎖的可重入次數,在默認情況下,state的值為0表示當前鎖沒有被任何線程持有。當一個線程第一次獲取該鎖時,會嘗試使用CAS設置state的值為1,
如果CAS成功則當前線程獲取了該鎖,然后記錄該鎖的持有者為當前線程。在該線程沒用釋放鎖的情況下第二次獲取該鎖后,狀態值被設置為2,這就是可重入次數。
在該線程釋放鎖時,會嘗試使用CAS讓狀態值減1,如果減1后狀態值為0,則當前線程釋放該鎖。
lock()獲取鎖,其實就是把state從0變成n(重入鎖可以累加)。實際調用的是sync的lock方法,分公平和非公平。
public void lock() { sync.lock(); }
在如上代碼中,ReentrantLock的lock()委托給sync類,根據創建的ReentrantLock構造函數選擇sync的實現是NonfairSync還是FairSync,先看看sync的子類NonfairSync(非公平鎖????)的情況
final void lock() { if (compareAndSetState(0, 1))//CAS設置狀態值為1 setExclusiveOwnerThread(Thread.currentThread());//設置該鎖的持有者為當前線程 else //CAS失敗的話 acquire(1);//調用AQS的acquire方法,傳遞參數為1 }
下面是AQS的acquire的核心源碼
public final void acquire(int arg) { if (!tryAcquire(arg) &&//調用ReentantLock重寫tryAcquire方法 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire返回false會把當前線程放入AQS阻塞隊列 selfInterrupt(); }
之前說過,AQS并沒有提供可用的tryAcquire方法,tryAcquire方法需要子類自己定制化,所以這里代碼會調用ReentantLock重寫的tryAcquire方法。我們看下非公平鎖????的實現
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {//當前AQS狀態為0,acquires參數傳遞默認為1,因為之前CAS失敗,再次獲取鎖 if (compareAndSetState(0, acquires)) {//CAS設置狀態值為1 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;//如果當前線程不是該鎖的持有者,則返回false,然后會放入AQS阻塞隊列 }
結束完非公平鎖????的實現代碼,回過頭來看看非公平在這里是怎么體現的。首先非公平是說先嘗試獲取鎖的線程并不一定比后嘗試獲取鎖的線程優先獲取鎖????。
而是使用了搶奪策略。那么下面我們看看公平鎖????是怎么實現公平的。
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {//當前AQS狀態為0 if (!hasQueuedPredecessors() &&//公平性策略,判斷隊列還有沒有其它node,要保證公平 compareAndSetState(0, acquires)) {//CAS設置狀態 setExclusiveOwnerThread(current);//設置獲取鎖的線程 return true; } } else if (current == getExclusiveOwnerThread()) {//如果當前線程是該鎖的持有者 int nextc = c + acquires;//重入次數+1 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc);//重新設置鎖的狀態 return true; } return false; } }
如上代碼所示,公平的tryAcquire策略與非公平的類似,不同之處在于,代碼在設置CAS操作之前添加了hasQueuedPredecessors()方法,該方法是實現公平性的核心代碼。代碼如下
public final boolean hasQueuedPredecessors() { 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()); }
該方法與lock()方法類似,不同在于對中斷進行響應,如果當前線程在調用該方法時,其它線程調用了當前線程的interrupt()方法,則該線程拋出異常而返回
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted())//如果當前線程被中斷,則直接拋出異常 throw new InterruptedException(); if (!tryAcquire(arg))//嘗試獲取資源 doAcquireInterruptibly(arg);//調用AQS可被中斷的方法 }
嘗試獲取鎖,如果當前鎖沒用被其它線程持有,則當前線程獲取該鎖并返回true,否則返回false。注意,該方法不會引起當前線程阻塞
public boolean tryLock() { return sync.nonfairTryAcquire(1); }
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; }
如上代碼與非公平鎖的tryAcquire()方法代碼類似,所以tryLock()使用的是非公平策略。
嘗試獲取鎖,與tryLock()的不同之處在于,它設置了超時時間,如果超時時間到了,沒用獲取到鎖,則返回false,以下是相關代碼
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout));//調用AQS的tryAcquireNanos方法 }
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return true; } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
嘗試獲取鎖,如果當前線程持有鎖,則調用該方法會讓該線程持有的AQS狀態值減1,如果減1后當前狀態值為0,則當前線程會釋放該鎖,否則僅僅減1而已。
如果當前線程沒用持有該鎖而調用了該方法則會拋出異常,代碼如下:
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; }
protected final boolean tryRelease(int releases) { int c = getState() - releases;//AQS狀態值減1 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {//如果當前可重入次數為0,則清空鎖持有線程 free = true; setExclusiveOwnerThread(null); } setState(c);//設置可重入次數為原始值減1 return free; }
下面使用ReentrantLock來實現一個簡單的線程安全的list集合
public class ReentrantLockList { //線程不安全的list private ArrayList<String>arrayList=new ArrayList<>(); //獨占鎖 private volatile ReentrantLock lock=new ReentrantLock(); //添加元素 public void add(String e){ lock.lock(); try { arrayList.add(e); }finally { lock.unlock(); } } //刪除元素 public void remove(String e){ lock.lock(); try { arrayList.remove(e); }finally { lock.unlock(); } } //獲取數據 public String get(int index){ lock.lock(); try { return arrayList.get(index); }finally { lock.unlock(); } } }
如上代碼在操作arrayList元素前進行加鎖保證同一時間只有一個線程可用對arrayList數組進行修改,但是也只能一個線程對arrayList進行訪問。
如圖,假如線程Thread-1,Thread-2,Thread-3同時嘗試獲取獨占鎖ReentrantLock,加上Thread-1獲取到了????,則Thread-2和Thread-3就會被轉換為Node節點并放入ReentrantLock對應的AQS阻塞隊列,而后阻塞掛起。
如圖,假設Thread-1獲取鎖后調用了對應的鎖創建的條件變量1,那么Thread-1就會釋放獲取到的????,然后當前線程就會被轉換為Node節點插入條件變量1的條件隊列。由于Thread-1釋放了????,所以阻塞到AQS隊列里面的
Thread-2和Thread-3就會有機會獲取到該鎖,假如使用的是公平性策略,那么者時候Thread-2會獲取到鎖,從而從AQS隊列里面移除Thread-2對應的Node節點。
“ReentrantLock源碼分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。