Java並發鎖控制API詳解


Java並發鎖控制API詳解

博客分類:
 

一.Lock接口(java.util.concurrent.locks):

  • void lock():獲取鎖,阻塞方式;如果資源已被其他線程鎖定,那么lock將會阻塞直到獲取鎖,鎖阻塞期間不受線程的Interrupt的影響,在獲取鎖成功后,才會檢測線程的interrupt狀態,如果interrupt=true,則拋出異常。
  • unlock():釋放鎖
  • tryLock():嘗試獲取鎖,並發環境中"闖入"行為,如果有鎖可用,直接獲取鎖並返回true,否則范圍false.
  • lockInterruptibly():嘗試獲取鎖,並支持"中斷"請求。與lock的區別時,此方法的開始、結束和執行過程中,都會不斷檢測線程的interrupt狀態,如果線程被中斷,則立即拋出異常;而不像lock方法那樣只會在獲取鎖之后才檢測。

二.Lock接口實現類

    Lock直接實現,只有3個類:ReentrantLock和WriteLock/ReadLock;這三種鎖;Lock和java的synchronized(內置鎖)的功能一致,均為排他鎖.

 

    ReentrantLock為重入排他鎖,對於同一線程,如果它已經持有了鎖,那么將不會再次獲取鎖,而直接可以使用.

    ReentrantReadWriteLock並沒有繼承ReentrantLock,而是一個基於Lock接口的單獨實現.它實現了                 ReadWriteLock,即讀寫分離鎖,是一種采用鎖分離技巧的API.

 

    盡管在API級別ReentrantReadWriteLock和ReentrantLock沒有直接繼承關系,但是ReentrantReadWriteLock中的ReadLock和WriteLock都具有ReentrantLock的全部語義(簡單說,就是把ReentrantLock的代碼copy了一下.),即鎖的可重入性.WriteLock支持Condition(條件),ReadLock不支持.

 

    Lock的實現類中,都包含了2中鎖等待策略:公平和非公平;其實他們的實現也非常簡單,底層都是使用了queue來維持鎖請求順序.[參考:http://shift-alt-ctrl.iteye.com/blog/1839142]

 

    公平鎖,就是任何鎖請求,首先將請求加入隊列,然后再有隊列機制來決定,是阻塞還是分配鎖.

非公平,就是允許"闖入",當然公平鎖,也無法干擾"闖入",對於任何鎖請求,首先檢測鎖狀態是否可用,如果可用直接獲取,否則加入隊列..

 

    ReentrantLock本質上和synchronized修飾詞是同一語義,如果一個線程lock()之后,其他線程進行lock時必須阻塞,直到當前線程的前續線程unlock.[執行lock操作時,將會被隊列化(假如在公平模式下),獲取lock的線程都將具有前續/后繼線程,前續線程就是當前線程之前執行lock操作而阻塞的線程,后繼線程就是當前線程之后執行lock操作的線程;那么對於unlock操作就是"解鎖"信號的傳遞,如果當前線程unlock,那么將會觸發后繼線程被"喚醒",即它因為lock操作阻塞狀態被解除.];這是ReentrantLock的基本原理,但是當ReentrantLock在Conditon情況下,事情就變得更加復雜.[參加下述]

 

三.Condition:鎖條件

    Condition與Lock形成happen-before關系。Condition將Object的監視器方法(wait,notify,notifyAll)分解成截然不同的對象,以便通過這些對象與任意Lock實現組合。使Lock具有等待“集合”的特性,或者“類型”;Lock替代了synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。(synchronized + object.wait對應Lock + Condition.await)

 

    Condition又稱條件隊列,為線程提供了一個含義,以便在某種狀態條件現在可能為true的其他線程通知它之前,一直掛起該線程。即多個線程,其中一個線程因為某個條件而阻塞,其他線程當“條件”滿足時,則“通知”哪些阻塞的線程。這,幾乎和object中wait和notify的機制一樣。

    Condition和wait一樣,阻塞時也將原子性的釋放鎖(間接執行了release()方法)。並掛起線程。Condition必須與Lock形成關系,只有獲取lock權限的,才能進行Condition操作。Condition底層基於AQS實現,條件阻塞,將以隊列的方式,LockSupport支持。其實現類有ConditionObject,這也是Lock.newCondition()的返回實際類型,在等待 Condition 時,允許發生“虛假喚醒”,這通常作為對基礎平台語義的讓步。對於大多數應用程序,這帶來的實際影響很小,因為 Condition 應該總是在一個循環中被等待,並測試正被等待的狀態聲明。某個實現可以隨意移除可能的虛假喚醒,但建議應用程序程序員總是假定這些虛假喚醒可能發生,因此總是在一個循環中等待。

  • void await() throws InterruptedException:當前線程阻塞,並原子性釋放對象鎖。如下條件將觸發線程喚醒:
  1. 當線程被中斷(支持中斷響應),
  2. 其他線程通過condition.signal()方法,且碰巧選中當前線程喚醒
  3. 其他線程通過condition.signalAll()方法
  4. 發生虛假喚醒

    底層實現,await()方法將當前線程信息添加到Conditon內部維護的"await"線程隊列的尾部(此隊列的目的就是為singal方法保持亟待喚醒的線程的順序),然后釋放鎖(執行tryRelease()方法,注意此處釋放鎖,僅僅是釋放了鎖信號,並不是unlock,此時其他線程仍不能獲取鎖--lock方法阻塞),然后使用LockSupport.park(this)來強制剝奪當前線程執行權限。await方法會校驗線程的中斷標記。

由此可見,await()方法執行之后,因為已經"歸還"了鎖信號,那么其他線程此時執行lock方法,將不再阻塞..

 

  • void awaitUninterruptibly():阻塞,直到被喚醒。此方法不響應線程中斷請求。即當線程被中斷時,它將繼續等待,直到接收到signal信號(你應該能想到"陷阱"),當最終從此方法返回時,仍然將設置其中斷狀態。
  • void signal()/signalAll():喚醒一個/全部await的線程。
    對於signal()方法而言,底層實現為,遍歷 await"線程隊列,找出此condition上最先阻塞的線程,並將此阻塞線程unpark.至此為止,我們似乎發現"鎖信號"丟失了,因為在線程await時通過tryRelease時釋放了一次信號.那么被signal成功的線程,首先執行一次acquire(增加鎖信號),然后校驗自己是否被interrupted,如果鎖信號獲取成功且線程狀態正常,此時才正常的從await()方法退出.經過這么復雜的分析,終於明白了ReentrantLock + Condition情況下,鎖狀態變更和線程控制的來龍去脈...
Java代碼   收藏代碼
  1. //////例子:  
  2. private Lock lock = new ReentrantLock();  
  3. private Condition full = lock.newCondition();  
  4. private Condition empty = lock.newCondition();  
  5. public Object take(){  
  6.     lock.lock();  
  7.     try{  
  8.         while(isEmpty()){  
  9.             empty.await()  
  10.         }  
  11.         Object o = get()  
  12.         full.signalAll();  
  13.         return o;  
  14.     }finally{  
  15.         lock.unlock();  
  16.     }  
  17. }  
  18.   
  19. public void put(Object o){  
  20.     lock.lock();  
  21.     try{  
  22.         while(isFull()){  
  23.             full.await();  
  24.         }  
  25.         put(o);  
  26.         empty.signalAll();  
  27.     }finally{  
  28.         lock.unlock();  
  29.     }  
  30. }  
 

四.機制

    Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。此實現允許更靈活的結構,可以具有差別很大的屬性,可以支持多個相關的 Condition 對象。注意,Lock 實例只是普通的對象,其本身可以在 synchronized 語句中作為目標使用。獲取 Lock 實例的監視器鎖與調用該實例的任何 lock() 方法沒有特別的關系。為了避免混淆,建議除了在其自身的實現中之外,決不要以這種方式使用 Lock 實例。

 

    Lock接口具有的方法:

  • void lock():獲取鎖,阻塞直到獲取。
  • void lockInterruptibly() throws InterrutedException:獲取鎖,阻塞直到獲取成功,支持中斷響應。
  • boolean tryLock():嘗試獲取鎖,返回是否獲取的結果。如果碰巧獲取成功,則返回true,此時已經持有鎖。
  • boolean tryLock(long time,TimeUnit) throws InterruptedException:嘗試獲取鎖,獲取成功返回true,超時時且沒有獲取鎖則返回false。
  • void unlock():釋放鎖。約定只有持有鎖者才能釋放鎖,否則拋出異常。
  • void newCondition():返回綁定到lock的條件。

 

五.ReadWriteLock

    ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。只要沒有 writer(寫鎖),讀取鎖可以由多個 reader 線程同時保持(共享鎖)。寫入鎖是獨占的。所有 ReadWriteLock 實現都必須保證 writeLock 操作的內存同步效果也要保持與相關 readLock 的聯系。也就是說,成功獲取讀鎖的線程會看到寫入鎖之前版本所做的所有更新。

    與互斥鎖相比,讀-寫鎖允許對共享數據進行更高級別的並發訪問。雖然一次只有一個線程(writer 線程)可以修改共享數據,但在許多情況下,任何數量的線程可以同時讀取共享數據(reader 線程),讀-寫鎖利用了這一點。從理論上講,與互斥鎖相比,使用讀-寫鎖所允許的並發性增強將帶來更大的性能提高。在實踐中,只有在多處理器上並且只在訪問模式適用於共享數據時,才能完全實現並發性增強。

  • Lock readLock():返回讀鎖。
  • Lock writeLock():返回寫鎖。

六.ReentrantLock

    ReentrantLock,重入排它鎖,它和synchronized具有相同的語義以及在監視器上具有相同的行為,但是功能更加強大。

    ReetrantLock將由最近成功獲得鎖且還沒有釋放鎖的線程標記為“鎖占有者”;當鎖沒有被線程持有時,調用lock方法將會成功獲取鎖並返回,如果當前線程為鎖持有者,再次調用lock將立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發生。

 

    ReentrantLock的構造方法,允許接收一個“公平策略”參數,“公平策略”下,多個線程競爭獲取鎖時,將會以隊列化鎖請求者,並將鎖授予隊列的head。在“非公平策略”下,則不完全保證鎖獲取的順序,允許闖入行為(tryLock)。

    ReentrantLock基於AQS機制,鎖信號量為1,如果信號量為1且當前鎖持有者不為自己,則不能獲取鎖。釋放鎖時,如果當前鎖持有者不是自己,也將拋出“IllegalMonitorStateException”。由此可見,對於ReentrantLock,lock和release方法是需要組合出現。

 

七.ReentrantReadWriteLock:可重入讀寫分離鎖

  1.  重入性 :當前線程可以重新獲取相應的“讀鎖”或者“寫鎖”,在寫入線程保持的所有寫入鎖都已經釋放后,才允許重入reader(讀取線程)使用它們。writer線程可以獲取讀鎖,但是reader線程卻不能直接獲取寫鎖。
  2. 鎖降級:重入還允許寫入鎖降級為讀鎖,其實現方式為:先獲取寫入鎖,然后獲取讀取鎖,最后釋放寫入鎖。但是讀取鎖不能升級為寫入鎖。
  3. Conditon的支持:只有寫入鎖支持conditon,對於讀取鎖,newConditon方法直接拋出UnsupportedOperationException。

    ReentrantReadWriteLock目前在java api中無直接使用。ReentrantReadWriteLock並沒有繼承自     ReentrantLock,而是單獨重新實現。其內部仍然支持“公平性”“非公平性”策略。

    ReentrantReadWriteLock基於AQS,但是AQS只有一個state來表示鎖的狀態,所以如果一個state表示2種類型的鎖狀態,它做了一個很簡單的策略,“位運算”,將一個int類型的state拆分為2個16位段,左端表示readlock鎖引用計數,右端16位表示write鎖。在readLock、writeLock進行獲取鎖或者釋放鎖時,均是通過有效的位運算和位控制,來達到預期的效果。

 

八.ReadLock

 

  • void lock():獲取讀取鎖,偽代碼如下: 
Java代碼   收藏代碼
  1. //如果當前已經有“寫鎖”,且持有寫鎖者不是當前線程(如果是當前線程,則支持寫鎖,降級為讀鎖),則獲取鎖失敗  
  2. //即任何讀鎖的獲取,必須等待隊列中的寫鎖釋放  
  3. //c為實際鎖引用量(exclusiveCount方法實現為:c & ((1<<16) -1)  
  4. if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)  
  5.    return -1;  
  6.  //CAS操作,操作state的左端16位。  
  7. if(CAS(c,c + (1<<16))){  
  8.     return 1;  
  9. }  

 

  • void unlock():釋放read鎖,即共享鎖,偽代碼如下:
Java代碼   收藏代碼
  1. //CAS鎖引用  
  2. for (;;) {  
  3.     int c = getState();  
  4.     int nextc = c - (1<<16);//位操作,釋放一個鎖。  
  5. if (compareAndSetState(c, nextc))  
  6.     return nextc == 0;  
  7. }  
 

九.WriteLock

  • void lock():獲取寫入鎖,偽代碼如下:

 

Java代碼   收藏代碼
  1. //當前線程  
  2. Thread current = Thread.currentThread();  
  3. //實際的鎖引用state  
  4. int c = getState();  
  5. //右端16位,通過位運算獲取“寫入鎖”的state  
  6. int w = exclusiveCount(c);  
  7. //如果有鎖引用  
  8. if (c != 0) {  
  9.     //且所引用不是自己  
  10.     if (w == 0 || current != getExclusiveOwnerThread()){  
  11.         return false;  
  12.     }  
  13. }  
  14.   
  15. //如果寫入鎖state為0,且CAS成功,則設置state和獨占線程信息  
  16. if ((w == 0 && writerShouldBlock(current)) ||!compareAndSetState(c, c + acquires)){  
  17.     return false;  
  18. }  
  19. setExclusiveOwnerThread(current);  
  20. return true;  

 

  • void unlock():釋放寫入鎖,偽代碼如下:

 

Java代碼   收藏代碼
  1. //計算釋放鎖的信號量  
  2. int nextc = getState() - releases;  
  3. //對於寫入鎖,則校驗當前線程是否為鎖持有者,否則不可以釋放(死鎖)  
  4. if (Thread.currentThread() != getExclusiveOwnerThread())  
  5.     throw new IllegalMonitorStateException();  
  6. //釋放鎖,且重置獨占線程信息  
  7. if (exclusiveCount(nextc) == 0) {  
  8.     setExclusiveOwnerThread(null);  
  9.     setState(nextc);  
  10.     return true;  
  11. else {  
  12.     setState(nextc);  
  13.     return false;  
  14. }  

 

十.LockSupport:用來創建鎖和其他同步類的基本線程阻塞原語。

    底層基於hotspot的實現unsafe。park 和 unpark 方法提供了阻塞和解除阻塞線程的有效方法。三種形式的 park(即park,parkNanos(Object blocker,long nanos),parkUntil(Object blocker,long timestamp)) 還各自支持一個 blocker 對象參數。此對象在線程受阻塞時被記錄,以允許監視工具和診斷工具確定線程受阻塞的原因。(這樣的工具可以使用方法 getBlocker(java.lang.Thread) 訪問 blocker。)建議最好使用這些形式,而不是不帶此參數的原始形式。

    在鎖實現中提供的作為 blocker 的普通參數是 this。

  • static void park(Object blocker):阻塞當前線程,直到如下情況發生:
  1. 其他線程,調用unpark方法,並將此線程作為目標而喚醒
  2. 其他線程中斷當前線程此方法不報告,此線程是何種原因被放回,需要調用者重新檢測,而且此方法也經常在while循環中執行 
Java代碼   收藏代碼
  1. while(//condition,such as:queue.isEmpty){  
  2.     LockSupport.park(queue);//此時queue對象作為“阻塞”點傳入,以便其他監控工具查看,queue的狀態  
  3.     //檢測當前線程是否已經中斷。  
  4.     if(Thread.interrupted()){  
  5.         break;  
  6.     }  
  7. }  

 

  • void getBlocker(Thread t):返回提供最近一次尚未解除阻塞的park的阻塞點。可以返回null。
  • void unpark(Thread t):解除指定線程阻塞,使其可用。參數null則無效果。

     LockSupport實例(不過不建議在實際代碼中直接使用LockSupport,很多時候,你可以使用鎖來控制):

 

Java代碼   收藏代碼
  1. /////////////Demo  
  2.   
  3. public class LockSupportTestMain {  
  4.   
  5.     /** 
  6.  
  7.     * @param args 
  8.  
  9.     */  
  10.   
  11.     public static void main(String[] args) throws Exception{  
  12.         System.out.println("Hear!");  
  13.         BlockerObject blocker = new BlockerObject();  
  14.         LThread tp = new LThread(blocker, false);  
  15.         LThread tt = new LThread(blocker, true);  
  16.         tp.start();  
  17.         tt.start();  
  18.         Thread.sleep(1000);  
  19.     }  
  20.   
  21.     static class LThread extends Thread{  
  22.         private BlockerObject blocker;  
  23.         boolean take;  
  24.         LThread(BlockerObject blocker,boolean take){  
  25.             this.blocker = blocker;  
  26.             this.take = take;  
  27.         }  
  28.   
  29.         @Override  
  30.         public void run(){  
  31.             if(take){  
  32.             while(true){  
  33.                 Object o = blocker.take();  
  34.                 if(o != null){  
  35.                     System.out.println(o.toString());  
  36.                 }  
  37.                 }  
  38.                 }else{  
  39.                     Object o = new Object();  
  40.                     System.out.println("put,,," + o.toString());  
  41.                     blocker.put(o);  
  42.                 }  
  43.         }  
  44.     }  
  45.   
  46.     static class BlockerObject{  
  47.         Queue<Object> inner = new LinkedList<Object>();  
  48.         Queue<Thread> twaiters = new LinkedList<Thread>();  
  49.         Queue<Thread> pwaiters = new LinkedList<Thread>();  
  50.         public void put(Object o){  
  51.             inner.offer(o);  
  52.             pwaiters.offer(Thread.currentThread());  
  53.             Thread t = twaiters.poll();  
  54.             if(t != null){  
  55.                 LockSupport.unpark(t);  
  56.             }  
  57.             System.out.println("park");  
  58.             LockSupport.park(Thread.currentThread());  
  59.             System.out.println("park is over");  
  60.         }  
  61.   
  62.         public Object take(){  
  63.             Thread t = pwaiters.poll();  
  64.             if(t != null){  
  65.                 System.out.println("unpark");  
  66.                 LockSupport.unpark(t);  
  67.                 System.out.println("unpark is OK");  
  68.             }  
  69.                 //twaiters.offer(Thread.currentThread());  
  70.                 return inner.poll();  
  71.             }  
  72.         }  
  73.   
  74. }  

 

 

備注:有時候會疑惑wait()/notify() 和Unsafe.park()/unpark()有什么區別?區別是wait和notify是Object類的方法,它們首選需要獲得“對象鎖”,並在synchronized同步快中執行。park和unpark怎不需要這么做。wait和park都是有當前線程發起,notify和unpark都是其他線程發起。wait針對的是對象鎖,park針對的線程本身,但是最終的效果都是導致當前線程阻塞。Unsafe不建議開發者直接使用。


免責聲明!

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



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