ReentrantLock-公平鎖、非公平鎖、互斥鎖、自旋鎖


  重入鎖,又稱遞歸鎖,是指在同一線程中,外部方法獲取鎖后,內層遞歸方法仍然可以獲取該鎖。如果鎖不具備重入性,那么當一個線程兩次獲取鎖的時候就會發生死鎖。java提供了java.util.concurrent.ReentrantLock來解決重入鎖問題。

  ReentrantLock重入鎖並不是容器集合類的一部分,但它在Concurrency包中占據了非常重要的一部分。在並發容器的實現中被大量使用。

  ReentrantLock是一種顯式鎖,與synchronized隱式鎖對應。synchronized不能顯式的對Lock對象進行操作,因此有很多不便利性。而顯式鎖提供了多種方法來操作Lock。

  1)       lock():獲取鎖,如果鎖不可用,那么當前線程會休眠直到獲取鎖為止。

  2)       lockInterruptibly():可中斷地獲取鎖,如果當前線程發生interrupt,則釋放鎖。

  3)       tryLock():嘗試獲取鎖,如果取到了,那么返回true,它與lock()的區別在於它不會休眠當前線程。

  4)       unlock():釋放鎖。

  5)       newCondition():創建一個當前鎖的條件監視器Condition,condition實例用於控制當前Lock的線程隊列的notify和wait。

  ReentrantLock的實現基於AQS,通過tryAcquire和tryRelease的重寫,實現了鎖機制和重入機制。

1,ReentrantLock的公平鎖與非公平鎖

  ReentrantLock在底層有兩種實現方式,分別是FairSync(公平鎖)和NonfairSync(非公平鎖),它們lock()流程如圖:

 

FairSync

1 static final class FairSync extends Sync { 2     private static final long serialVersionUID = -300003432432432L; 3     final void lock() { 4         //FairSync直接調用acquire方法來獲取鎖
5         acquire(1); 6  } 7     protected final boolean tryAcqure(int acquires) {...} 8 }

  FairSync與NonfairSync都會調用同樣的acquire方法,因此有必要了解一下acquire方法的實現:

1 public final void acquire(int arg) { 2     //只需要注意tryAcquire()方法,它用於請求鎖,返回true時后續的操作不再被處理
3     if(!tryAcquire(arg) && acquireQueued(addWaiter(Node, EXCLUSIVE), arg)) { 4  selfInterrupt(); 5  } 6 }

       tryAcquire用於請求鎖,當請求失敗的時候,會把當前線程加入等待隊列,addWaiter()和acquiredQueued()方法分別對應封裝等待線程節點和請求入隊操作。

NonfairSync

 1 static final class NonfairSync extends Sync {  2     private static final long serialVesionUID = 4324242432L;  3     final void lock() {  4         //驗證當前鎖狀態,如果是0,那么設置1  5         //狀態為0說明沒有其他線程持有鎖,當前線程可以直接獲得鎖  6         //setExclusiveOwnerThread即為了當前排它鎖執行所有者線程方法
 7         if(compareAndSetState(0, 1)) {  8  setExclusiveOwnerThread(Thread.currentThread());  9         } else { 10             //狀態不為0,則說明其他線程持有鎖,執行獲取鎖的方法acquire 11             //該方法最終用於獲取鎖的方法是tryAcquire
12  acquire(); 13  } 14         
15         protected fianl boolean tryAcquire(int acquires) { 16             return nonfairTryAcquire(acquires); 17  } 18  } 19 }


  1)       NonFairSync類在lock()方法調用的第一時間,直接驗證當前鎖狀態,如果沒有其它線程持有鎖(鎖狀態state為0),那么當前線程會持有鎖。NonfairSync與fairSync主要區別為:

  2)       NonFairSync類的tryAcquire()方法執行不同,它直接調用了nonfairTryAcquire()方法,nonfairTryAcquire()方法不要求嚴格按照等待隊列的入隊順序獲取鎖。

下面來看一下FairSync.tryAcquire()和NonFairSync.nonfairTryAcquire():

 1 protected final boolean tryAcquire(int acquires) {  2     final Thread current = Thread.currentThread();  3     int c = getState();  4     if(c == 0) {  5         //注意這個hasQueuedPredecessors()方法,只有FairSync才會調用它  6         //它是FairSync和NonFairSync僅有的區別
 7         if(!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {  8  setExclusiveOwnerThread(current);  9             return true; 10         } else if(current == getExclusiveOwnerThread()) {...} 11         return false; 12  } 13 }

  1)       FairSync保證了FIFO,先入隊的等待線程會先獲得鎖,而NonfairSync任由各個等待線程競爭。

  2)       由於FairSync要保證有序性,所以NonfairSync的性能更高,ReentrantLock默認使用NonfairSync。

2,ReentrantLock的重入性

       加鎖有兩種基本形式,互斥鎖與自旋鎖。

       互斥鎖(Mutex),通過阻塞線程來進行加鎖,中斷阻塞來進行解鎖。

 1 public class MutexLock {  2     private AtomicReference<Thread> owner = new AtomicReference<>();  3     private LinkedList<Thread> list = new LinkedList<>();  4     public void lock() {  5         Thread currentThread = Thread.currentThread();  6         //沒有任何線程持有鎖時,讓當前線程持有鎖,反之則加入等待隊列並阻塞
 7         if(!owner.compareAndSet(null, currentThread)) {  8  waiterQueue.add(currentThread);  9             //LockSupport阻塞當前線程
10  LockSupport.park(); 11  } 12  } 13     public void unlock() { 14         //如果解鎖的線程不是持有鎖的線程,那么拋出異常
15         if(Thread.currentThread() != owner.get()) { 16             throw new RuntimeException(); 17  } 18         //等待隊列里有內容時,恢復隊頭線程,更改持有鎖的線程,反之則直接釋放鎖
19         if(waiterQueue.size() > 0) { 20             Thread t = waiterQueue.poll(); 21  owner.set(t); 22             //LockSupport釋放指定線程
23  LockSupport.unpark(t); 24         } else { 25             owner.set(null); 26  } 27  } 28 }

  自旋鎖(Spin lock),線程保持運行態,用一個循環體不停地判斷某個標質量的狀態來確定加鎖還是解鎖,本質上用一段無意義的死循環來阻塞線程運行。

 1 public class SpinLock {  2     private AtomicReference<Thread> owner = new AtomicReference<>();  3     public void lock() {  4         Thread current = Thread.currentThread();  5         //沒有任何線程持有鎖時,讓當前線程持有鎖,反之則利用循環來阻塞
 6         while (!owner.compareAndSet(null, current)) { }  7  }  8     public void unlock() {  9         Thread current = Thread.currentThread(); 10         //釋放鎖
11         owner.compareAndSet(current, null); 12  } 13 }

       無論哪種實現方式,都回避不了一個問題,那就是在同一個線程中,如果遞歸地獲取相同的鎖,都會出現死鎖。設想線程A持有了鎖,在釋放之前,A再次請求加鎖,此時由於鎖擁有了持有者,於是由於鎖擁有了持有者(A自己),於是A被阻塞了。因此需要引入重入鎖:       自旋鎖(Spin lock),線程保持運行態,用一個循環體不停地判斷某個標質量的狀態來確定加鎖還是解鎖,本質上用一段無意義的死循環來阻塞線程運行。

  1)       在線程持有鎖的時候,其它線程不能訪問上鎖的共享資源。

  2)       在線程持有鎖的時候,線程本身可以繼續訪問上鎖的共享資源。

  3)       在多次遞歸訪問中,只有當全部訪問都結束了,線程才會釋放鎖。

  由此可以想到一個很直觀的解決方式——計數器,對持有鎖的線程的每一次訪問進行計數,只有當訪問次數清空之后,其他線程才能繼續訪問。

 1 public class MutexLock {  2     private AtomicReference<Thread> owner = new AtomicReference<>();  3     private LinkedList<Thread> waiterQueue = new LinkedList<>();  4     private volatile AtomicInteger state = new AtomicInteger(0);  5     public void lock() {  6         Thread currentThread = Thread.currentThread();  7         //如果請求鎖的線程是當前線程
 8         if(owner.get() == currentThread) {  9  state.incrementAndGet(); 10             return; 11  } 12         //沒有任何線程持有鎖時,讓當前線程持有鎖,反之則加入等待隊列並阻塞
13         if(!owner.compareAndSet(null, currentThread)) { 14  waiterQueue.add(currentThread); 15             //LockSupport阻塞當前線程
16  LockSupport.park(); 17  } 18  } 19     public void unlock() { 20         //如果解鎖的線程不是持有鎖的線程,那么拋出異常
21         if(Thread.currentThread() != owner.get()) { 22             throw new RuntimeException(); 23  } 24         //計數器清空之后才能繼續之后的操作
25         if(state.get() > 0) { 26  state.decrementAndGet(); 27             return; 28  } 29         //等待隊列里有內容時,釋放指定隊列,更改持有鎖的線程,反之則清空持有鎖的線程
30         if(waiterQueue.size() > 0) { 31             Thread t = waiterQueue.poll(); 32  owner.set(t); 33             //LockSupport釋放指定線程
34  LockSupport.unpark(t); 35         } else { 36             owner.set(null); 37  } 38  } 39 }

  FairSync.tryAcquire()和NonfairSync.nofairTryAcquire()有重用部分,無論公平鎖還是非公平鎖,在處理重入上,代碼是一致的:

  1)       判斷state標量是否為0,如果為0,那么說明沒有線程持有該鎖,當前線程可以持有鎖,返回true;FairSync.tryAcquire()和NonfairSync.nofairTryAcquire()有重用部分,無論公平鎖還是非公平鎖,在處理重入上,代碼是一致的:

  2)       如果state不為0,那么判斷當前線程是否為鎖持有者。

  3)       如果不是,那么當前線程不能持有鎖,返回false;

  4)       如果是,那么當前線程已經持有鎖,此時認同線程請求次數增加,state需要增加acquires次,acquires表示新增的請求鎖次數。

 1 final boolean nonfairTryAcquire(int acquires) {  2     final Thread current = Thread.currentThread();  3     int c = getState();  4     if(c == 0) {  5         //...
 6     } else if(current == getExclusiveOwnerThread()) {  7         int nextc = c + acquires;  8         if(nextc < 0) {  9             throw new Error("Maximum lock count exceeded"); 10  } 11  setState(nextc); 12         return true; 13  } 14 }

       tryRelease()方法有一個整型參數releases形參,用來表示本次釋放鎖的次數,如果當前線程不是鎖持有者,那么說明這是一次非法調用,當state計數歸零的時候,調用setExclusiveOwnerThread(null),用來表示沒有線程持有鎖了,此后鎖可以被任意調用。

 1 protected final boolean tryRelease(int releases) {  2     int c = getState() - releases;  3     if(Thread.currentThread() != getExclusiveOwnerThread()) {  4         throw new IllegalMonitorStateException;  5  }  6     boolean free = false;  7     if(c == 0) {  8         free = true;  9         setExclusiveOwnerThread(null); 10  } 11  setState(c); 12     return free; 13 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM