一.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:當前線程阻塞,並原子性釋放對象鎖。如下條件將觸發線程喚醒:
- 當線程被中斷(支持中斷響應),
- 其他線程通過condition.signal()方法,且碰巧選中當前線程喚醒
- 其他線程通過condition.signalAll()方法
- 發生虛假喚醒
底層實現,await()方法將當前線程信息添加到Conditon內部維護的"await"線程隊列的尾部(此隊列的目的就是為singal方法保持亟待喚醒的線程的順序),然后釋放鎖(執行tryRelease()方法,注意此處釋放鎖,僅僅是釋放了鎖信號,並不是unlock,此時其他線程仍不能獲取鎖--lock方法阻塞),然后使用LockSupport.park(this)來強制剝奪當前線程執行權限。await方法會校驗線程的中斷標記。
由此可見,await()方法執行之后,因為已經"歸還"了鎖信號,那么其他線程此時執行lock方法,將不再阻塞..
- void awaitUninterruptibly():阻塞,直到被喚醒。此方法不響應線程中斷請求。即當線程被中斷時,它將繼續等待,直到接收到signal信號(你應該能想到"陷阱"),當最終從此方法返回時,仍然將設置其中斷狀態。
- void signal()/signalAll():喚醒一個/全部await的線程。
- //////例子:
- private Lock lock = new ReentrantLock();
- private Condition full = lock.newCondition();
- private Condition empty = lock.newCondition();
- public Object take(){
- lock.lock();
- try{
- while(isEmpty()){
- empty.await()
- }
- Object o = get()
- full.signalAll();
- return o;
- }finally{
- lock.unlock();
- }
- }
- public void put(Object o){
- lock.lock();
- try{
- while(isFull()){
- full.await();
- }
- put(o);
- empty.signalAll();
- }finally{
- lock.unlock();
- }
- }
四.機制
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:可重入讀寫分離鎖
- 重入性 :當前線程可以重新獲取相應的“讀鎖”或者“寫鎖”,在寫入線程保持的所有寫入鎖都已經釋放后,才允許重入reader(讀取線程)使用它們。writer線程可以獲取讀鎖,但是reader線程卻不能直接獲取寫鎖。
- 鎖降級:重入還允許寫入鎖降級為讀鎖,其實現方式為:先獲取寫入鎖,然后獲取讀取鎖,最后釋放寫入鎖。但是讀取鎖不能升級為寫入鎖。
- 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():獲取讀取鎖,偽代碼如下:
- //如果當前已經有“寫鎖”,且持有寫鎖者不是當前線程(如果是當前線程,則支持寫鎖,降級為讀鎖),則獲取鎖失敗
- //即任何讀鎖的獲取,必須等待隊列中的寫鎖釋放
- //c為實際鎖引用量(exclusiveCount方法實現為:c & ((1<<16) -1)
- if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)
- return -1;
- //CAS操作,操作state的左端16位。
- if(CAS(c,c + (1<<16))){
- return 1;
- }
- void unlock():釋放read鎖,即共享鎖,偽代碼如下:
- //CAS鎖引用
- for (;;) {
- int c = getState();
- int nextc = c - (1<<16);//位操作,釋放一個鎖。
- if (compareAndSetState(c, nextc))
- return nextc == 0;
- }
九.WriteLock
- void lock():獲取寫入鎖,偽代碼如下:
- //當前線程
- Thread current = Thread.currentThread();
- //實際的鎖引用state
- int c = getState();
- //右端16位,通過位運算獲取“寫入鎖”的state
- int w = exclusiveCount(c);
- //如果有鎖引用
- if (c != 0) {
- //且所引用不是自己
- if (w == 0 || current != getExclusiveOwnerThread()){
- return false;
- }
- }
- //如果寫入鎖state為0,且CAS成功,則設置state和獨占線程信息
- if ((w == 0 && writerShouldBlock(current)) ||!compareAndSetState(c, c + acquires)){
- return false;
- }
- setExclusiveOwnerThread(current);
- return true;
- void unlock():釋放寫入鎖,偽代碼如下:
- //計算釋放鎖的信號量
- int nextc = getState() - releases;
- //對於寫入鎖,則校驗當前線程是否為鎖持有者,否則不可以釋放(死鎖)
- if (Thread.currentThread() != getExclusiveOwnerThread())
- throw new IllegalMonitorStateException();
- //釋放鎖,且重置獨占線程信息
- if (exclusiveCount(nextc) == 0) {
- setExclusiveOwnerThread(null);
- setState(nextc);
- return true;
- } else {
- setState(nextc);
- return false;
- }
十.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):阻塞當前線程,直到如下情況發生:
- 其他線程,調用unpark方法,並將此線程作為目標而喚醒
- 其他線程中斷當前線程此方法不報告,此線程是何種原因被放回,需要調用者重新檢測,而且此方法也經常在while循環中執行
- while(//condition,such as:queue.isEmpty){
- LockSupport.park(queue);//此時queue對象作為“阻塞”點傳入,以便其他監控工具查看,queue的狀態
- //檢測當前線程是否已經中斷。
- if(Thread.interrupted()){
- break;
- }
- }
- void getBlocker(Thread t):返回提供最近一次尚未解除阻塞的park的阻塞點。可以返回null。
- void unpark(Thread t):解除指定線程阻塞,使其可用。參數null則無效果。
LockSupport實例(不過不建議在實際代碼中直接使用LockSupport,很多時候,你可以使用鎖來控制):
- /////////////Demo
- public class LockSupportTestMain {
- /**
- * @param args
- */
- public static void main(String[] args) throws Exception{
- System.out.println("Hear!");
- BlockerObject blocker = new BlockerObject();
- LThread tp = new LThread(blocker, false);
- LThread tt = new LThread(blocker, true);
- tp.start();
- tt.start();
- Thread.sleep(1000);
- }
- static class LThread extends Thread{
- private BlockerObject blocker;
- boolean take;
- LThread(BlockerObject blocker,boolean take){
- this.blocker = blocker;
- this.take = take;
- }
- @Override
- public void run(){
- if(take){
- while(true){
- Object o = blocker.take();
- if(o != null){
- System.out.println(o.toString());
- }
- }
- }else{
- Object o = new Object();
- System.out.println("put,,," + o.toString());
- blocker.put(o);
- }
- }
- }
- static class BlockerObject{
- Queue<Object> inner = new LinkedList<Object>();
- Queue<Thread> twaiters = new LinkedList<Thread>();
- Queue<Thread> pwaiters = new LinkedList<Thread>();
- public void put(Object o){
- inner.offer(o);
- pwaiters.offer(Thread.currentThread());
- Thread t = twaiters.poll();
- if(t != null){
- LockSupport.unpark(t);
- }
- System.out.println("park");
- LockSupport.park(Thread.currentThread());
- System.out.println("park is over");
- }
- public Object take(){
- Thread t = pwaiters.poll();
- if(t != null){
- System.out.println("unpark");
- LockSupport.unpark(t);
- System.out.println("unpark is OK");
- }
- //twaiters.offer(Thread.currentThread());
- return inner.poll();
- }
- }
- }
備注:有時候會疑惑wait()/notify() 和Unsafe.park()/unpark()有什么區別?區別是wait和notify是Object類的方法,它們首選需要獲得“對象鎖”,並在synchronized同步快中執行。park和unpark怎不需要這么做。wait和park都是有當前線程發起,notify和unpark都是其他線程發起。wait針對的是對象鎖,park針對的線程本身,但是最終的效果都是導致當前線程阻塞。Unsafe不建議開發者直接使用。