java.util.concurrent.locks包爲鎖和等待條件提供一個框架的接口和類,它不一樣於內置同步和監視器。該框架容許更靈活地使用鎖和條件,但以更難用的語法爲代價。 javascript
Lock 接口支持那些語義不一樣(重入、公平等)的鎖規則,能夠在非阻塞式結構的上下文(包括 hand-over-hand 和鎖重排算法)中使用這些規則。主要的實現是 ReentrantLock。 java
ReadWriteLock 接口以相似方式定義了一些讀取者能夠共享而寫入者獨佔的鎖。此包只提供了一個實現,即 ReentrantReadWriteLock,由於它適用於大部分的標準用法上下文。但程序員能夠建立本身的、適用於非標準要求的實現。 程序員
Condition 接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用 Object.wait 訪問的隱式監視器相似,但提供了更強大的功能。須要特別指出的是,單個 Lock 可能與多個 Condition 對象關聯。爲了不兼容性問題,Condition 方法的名稱與對應的 Object 版本中的不一樣。 算法
如下是locks包的相關類圖: 數據庫
在以前咱們同步一段代碼或者對象時都是使用 synchronized關鍵字,使用的是Java語言的內置特性,然而 synchronized的特性也致使了不少場景下出現問題,好比: 編程
在一段同步資源上,首先線程A得到了該資源的鎖,並開始執行,此時其餘想要操做此資源的線程就必須等待。若是線程A由於某些緣由而處於長時間操做的狀態,好比等待網絡,反覆重試等等。那麼其餘線程就沒有辦法及時的處理它們的任務,只能無限制的等待下去。若是線程A的鎖在持有一段時間後可自動被釋放,那麼其餘線程不就可使用該資源了嗎?再有就是相似於數據庫中的共享鎖與排它鎖,是否也能夠應用到應用程序中?因此引入Lock機制就能夠很好的解決這些問題。 緩存
Lock提供了比 synchronized更多的功能。可是要注意如下幾點: 網絡
• Lock不是Java語言內置的,synchronized是Java語言的關鍵字,所以是內置特性。Lock是一個類,經過這個類能夠實現同步訪問; 併發
• Lock和synchronized有一點很是大的不一樣,採用 synchronized不須要用戶去手動釋放鎖,當synchronized方法或者 synchronized代碼塊執行完以後,系統會自動讓線程釋放對鎖的佔用;而 Lock則必需要用戶去手動釋放鎖,若是沒有主動釋放鎖,就有可能致使出現死鎖現象。 app
1、Condition
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意 Lock 實現組合使用,爲每一個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。
Condition(也稱爲條件隊列 或條件變量)爲線程提供了一種手段,在某個狀態條件下直到接到另外一個線程的通知,一直處於掛起狀態(即「等待」)。由於訪問此共享狀態信息發生在不一樣的線程中,因此它必須受到保護,所以要將某種形式的鎖與 Condition相關聯。
Condition 實例實質上被綁定到一個鎖上。要爲特定 Lock 實例得到 Condition 實例,可使用其 newCondition() 方法,如下是API中的實例:
做爲一個示例,假定有一個綁定的緩衝區,它支持 put 和 take 方法。若是試圖在空的緩衝區上執行 take 操做,則在某一個項變得可用以前(寫操做前),線程將一直阻塞;若是試圖在滿的緩衝區上執行 put 操做,則在有空間變得可用以前(讀操做前),線程將一直阻塞。從程序的運行結果就能夠看出,緩衝區未滿以前寫操做持續進行,此時讀操做會被阻塞沒法讀取數據。當緩衝區寫滿後寫線程喚醒讀線程,讓其開始工做,此時讀線程將緩衝區中的數據不斷取出,直至緩衝區中全部數據都被取出,以後讀線程進行等待並通知寫操做開始工做。
在Condition中,用 await()代替 wait(),用 signal()代替 notify(),用 signalAll()代替 notifyAll(),傳統線程的通訊方式,Condition均可以實現,Condition的強大之處在於它能夠爲多個線程間創建不一樣的 Condition。
Condition 實現能夠提供不一樣於 Object 監視器方法的行爲和語義,好比受保證的通知排序,或者在執行通知時不須要保持一個鎖。若是某個實現提供了這樣特殊的語義,則該實現必須記錄這些語義。
注意,Condition 實例只是一些普通的對象,它們自身能夠用做 synchronized 語句中的目標,而且能夠調用本身的 wait 和 notification 監視器方法。獲取 Condition 實例的監視器鎖或者使用其監視器方法,與獲取和該 Condition 相關的 Lock 或使用其 waiting 和 signalling 方法沒有什麼特定的關係。爲了不混淆,建議除了在其自身的實現中以外,切勿以這種方式使用 Condition 實例。
除非另行說明,不然爲任何參數傳遞 null 值將致使拋出 NullPointerException。
Condition方法很少,以前已經提到了await和 signal兩個方法,如下是 Condition的所有方法:
Condition 接口有兩個已知實現類:
AbstractQueuedLongSynchronizer 的屬性和方法與 AbstractQueuedSynchronizer 徹底相同,但全部與狀態相關的參數和結果都定義爲 long 而不是 int。當建立須要 64 位狀態的多級別鎖和屏障等同步器時,此類頗有用。由於兩個類基本相同,因此本文就只以 AbstractQueuedSynchronizer 的實現爲例。
在等待 Condition 時,容許發生「虛假喚醒」,這一般做爲對基礎平臺語義的讓步。對於大多數應用程序,這帶來的實際影響很小,由於 Condition 應該老是在一個循環中被等待,並測試正被等待的狀態聲明。某個實現能夠隨意移除可能的虛假喚醒,但建議應用程序程序員老是假定這些虛假喚醒可能發生,所以老是在一個循環中等待。
2、Lock
Lock也是一個接口,它 實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。此實現容許更靈活的結構,能夠具備差異很大的屬性,能夠支持多個相關的 Condition 對象。
Lock 實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。此實現容許更靈活的結構,能夠具備差異很大的屬性,能夠支持多個相關的 Condition 對象。
鎖是控制多個線程對共享資源進行訪問的工具。一般,鎖提供了對共享資源的獨佔訪問。一次只能有一個線程得到鎖,對共享資源的全部訪問都須要首先得到鎖。不過,某些鎖可能容許對共享資源併發訪問,如 ReadWriteLock 的讀取鎖。
synchronized 方法或語句的使用提供了對與每一個對象相關的隱式監視器鎖的訪問,但卻強制全部鎖獲取和釋放均要出如今一個塊結構中:當獲取了多個鎖時,它們必須以相反的順序釋放,且必須在與全部鎖被獲取時相同的詞法範圍內釋放全部鎖。
雖然 synchronized 方法和語句的範圍機制使得使用監視器鎖編程方便了不少,並且還幫助避免了不少涉及到鎖的常見編程錯誤,但有時也須要以更爲靈活的方式使用鎖。例如,某些遍歷併發訪問的數據結果的算法要求使用 "hand-over-hand" 或 "chain locking":獲取節點 A 的鎖,而後再獲取節點 B 的鎖,而後釋放 A 並獲取 C,而後釋放 B 並獲取 D,依此類推。Lock 接口的實現容許鎖在不一樣的做用範圍內獲取和釋放,並容許以任何順序獲取和釋放多個鎖,從而支持使用這種技術。
隨着靈活性的增長,也帶來了更多的責任。不使用塊結構鎖就失去了使用 synchronized 方法和語句時會出現的鎖自動釋放功能。在大多數狀況下,應該使用如下語句:
鎖定和取消鎖定出如今不一樣做用範圍中時,必須謹慎地確保保持鎖定時所執行的全部代碼用 try-finally 或 try-catch 加以保護,以確保在必要時釋放鎖。
Lock 實現提供了使用 synchronized 方法和語句所沒有的其餘功能,包括提供了一個非塊結構的獲取鎖嘗試 (tryLock())、一個獲取可中斷鎖的嘗試 (lockInterruptibly()) 和一個獲取超時失效鎖的嘗試 (tryLock(long, TimeUnit))。
Lock 類還能夠提供與隱式監視器鎖徹底不一樣的行爲和語義,如保證排序、非重入用法或死鎖檢測。若是某個實現提供了這樣特殊的語義,則該實現必須對這些語義加以記錄。
注意,Lock 實例只是普通的對象,其自己能夠在 synchronized 語句中做爲目標使用。獲取 Lock 實例的監視器鎖與調用該實例的任何 lock() 方法沒有特別的關係。爲了不混淆,建議除了在其自身的實現中以外,決不要以這種方式使用 Lock 實例。
除非另有說明,不然爲任何參數傳遞 null 值都將致使拋出 NullPointerException。
Lock接口有6個方法,分別是:
其中 lock與 unlock是最經常使用的方法,分別是獲取與釋放鎖。
如下是幾個方法的詳細解釋及用法。
1.newCondition() 方法
返回綁定到此 Lock 實例的新 Condition 實例。 在等待條件前,鎖必須由當前線程保持。調用 Condition.await() 將在等待前以原子方式釋放鎖,並在等待返回前從新獲取鎖。 實現時須要注意:Condition 實例的具體操做依賴於 Lock 實現,而且該實現必須對此加以記錄。
2.lock() 方法
獲取鎖,若是鎖已被其餘線程獲取,則進行等待。
若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在得到鎖以前,該線程將一直處於休眠狀態。以前已經說過若是採用Lock,必須主動去釋放鎖,而且在發生異常時,並不會自動釋放鎖。因此將釋放鎖的操做放在 try-finally 或 try-catch塊中進行,以保證鎖必定被被釋放,防止死鎖的發生。
如下是一個示例來表現未釋放鎖的狀況下的代碼執行狀況:
從結果就能夠清晰的看到,A線程獲取鎖以後並無主動釋放鎖,而後 B線程開始執行,此時 B嘗試獲取鎖,由於 A仍是鎖的持有者,因此 B只好等待。
將註釋去掉,打印出來的正常結果應該是:
3.unlock() 方法
釋放鎖。
實現時須要注意:Lock 實現一般對哪一個線程能夠釋放鎖施加了限制(一般只有鎖的保持者能夠釋放它),若是違背了這個限制,可能會拋出(未經檢查的)異常。該 Lock 實現必須對全部限制和異常類型進行記錄。
4.tryLock() 方法
僅在調用時鎖爲空閒狀態才獲取該鎖。
若是鎖可用,則獲取鎖,並當即返回值 true。若是鎖不可用,則此方法將當即返回值 false。 也就說這個方法不管如何都會當即返回,在拿不到鎖時不會一直在那等待,這點與lock不一樣。
tryLock的典型使用方法以下:
此用法可確保若是獲取了鎖,則會釋放鎖,若是未獲取鎖,則不會試圖將其釋放。
5.tryLock(long time, TimeUnit unit) 方法
若是鎖在給定的等待時間內空閒,而且當前線程未被中斷,則獲取鎖。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是相似的,只不過區別在於這個方法在拿不到鎖時會等待必定的時間,在時間期限以內若是還拿不到鎖,就返回false。若是若是一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在發生如下三種狀況之一前,該線程將一直處於休眠狀態:
• 鎖由當前線程得到;
• 其餘某個線程中斷當前線程,而且支持對鎖獲取的中斷;
• 已超過指定的等待時間。
若是當前線程:
• 在進入此方法時已經設置了該線程的中斷狀態;
• 在獲取鎖時被中斷,而且支持對鎖獲取的中斷, 則將拋出 InterruptedException,並會清除當前線程的已中斷狀態。
若是超過了指定的等待時間,則將返回值 false。若是 time 小於等於 0,該方法將徹底不等待。
實現時須要注意:在某些實現中可能沒法中斷鎖獲取,即便可能,該操做的開銷也很大。程序員應該知道可能會發生這種狀況。在這種狀況下,該實現應該對此進行記錄。
相對於普通方法返回而言,實現可能更喜歡響應某個中斷,或者報告出現超時狀況。
Lock 實現可能能夠檢測鎖的錯誤用法,例如,某個調用可能致使死鎖,在特定的環境中可能拋出(未經檢查的)異常。該 Lock 實現必須對環境和異常類型進行記錄。
6.lockInterruptibly() 方法
若是當前線程未被中斷,則獲取鎖。
lockInterruptibly()方法比較特殊,當經過這個方法去獲取鎖時,若是線程正在等待獲取鎖,則這個線程可以響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時經過 lock.lockInterruptibly()想獲取某個鎖時,倘若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用 threadB.interrupt()方法可以中斷線程B的等待過程。
若是鎖可用,則獲取鎖,並當即返回。 若是鎖不可用,出於線程調度目的,將禁用當前線程,而且在發生如下兩種狀況之一之前,該線程將一直處於休眠狀態:
• 鎖由當前線程得到;
• 其餘某個線程中斷當前線程,而且支持對鎖獲取的中斷。
若是當前線程:
• 在進入此方法時已經設置了該線程的中斷狀態;
• 在獲取鎖時被中斷,而且支持對鎖獲取的中斷, 則將拋出 InterruptedException,並清除當前線程的已中斷狀態。
實現時須要注意:在某些實現中可能沒法中斷鎖獲取,即便可能,該操做的開銷也很大。程序員應該知道可能會發生這種狀況。在這種狀況下,該實現應該對此進行記錄。
相對於普通方法返回而言,實現可能更喜歡響應某個中斷。 Lock 實現可能能夠檢測鎖的錯誤用法,例如,某個調用可能致使死鎖,在特定的環境中可能拋出(未經檢查的)異常。該 Lock 實現必須對環境和異常類型進行記錄。
因爲lockInterruptibly()的聲明中拋出了異常,因此lock.lockInterruptibly()必須放在try塊中或者在調用lockInterruptibly()的方法外聲明拋出InterruptedException。因此 lockInterruptibly()通常這樣使用:
3、ReadWriteLock
ReadWriteLock 維護了一對相關的鎖,一個用於只讀操做,另外一個用於寫入操做。只要沒有 writer,讀取鎖能夠由多個 reader 線程同時保持,而寫入鎖是獨佔的。
全部 ReadWriteLock 實現都必須保證 writeLock 操做的內存同步效果也要保持與相關 readLock 的聯繫。也就是說,成功獲取讀鎖的線程會看到寫入鎖以前版本所作的全部更新。
與互斥鎖相比,讀-寫鎖容許對共享數據進行更高級別的併發訪問。雖然一次只有一個線程(writer 線程)能夠修改共享數據,但在許多狀況下,任何數量的線程能夠同時讀取共享數據(reader 線程),讀-寫鎖利用了這一點。從理論上講,與互斥鎖相比,使用讀-寫鎖所容許的併發性加強將帶來更大的性能提升。在實踐中,只有在多處理器上而且只在訪問模式適用於共享數據時,才能徹底實現併發性加強。
與互斥鎖相比,使用讀-寫鎖可否提高性能則取決於讀寫操做期間讀取數據相對於修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操做的線程數。例如,某個最初用數據填充而且以後不常常對其進行修改的 collection,由於常常對其進行搜索(好比搜索某種目錄),因此這樣的 collection 是使用讀-寫鎖的理想候選者。可是,若是數據更新變得頻繁,數據在大部分時間都被獨佔鎖,這時,就算存在併發性加強,也是微不足道的。更進一步地說,若是讀取操做所用時間過短,則讀-寫鎖實現(它自己就比互斥鎖複雜)的開銷將成爲主要的執行成本,在許多讀-寫鎖實現仍然經過一小段代碼將全部線程序列化時更是如此。最終,只有經過分析和測量,才能肯定應用程序是否適合使用讀-寫鎖。
ReadWriteLock 接口很是簡單,只有兩個方法:
儘管讀-寫鎖的基本操做是直截了當的,但實現仍然必須做出許多決策,這些決策可能會影響給定應用程序中讀-寫鎖的效果。這些策略的例子包括:
• 在 writer 釋放寫入鎖時,reader 和 writer 都處於等待狀態,在這時要肯定是授予讀取鎖仍是授予寫入鎖。Writer 優先比較廣泛,由於預期寫入所需的時間較短而且不那麼頻繁。Reader 優先不太廣泛,由於若是 reader 正如預期的那樣頻繁和持久,那麼它將致使對於寫入操做來講較長的時延。公平或者「按次序」實現也是有可能的。
• 在 reader 處於活動狀態而 writer 處於等待狀態時,肯定是否向請求讀取鎖的 reader 授予讀取鎖。Reader 優先會無限期地延遲 writer,而 writer 優先會減小可能的併發。
• 肯定是否從新進入鎖:可使用帶有寫入鎖的線程從新獲取它嗎?能夠在保持寫入鎖的同時獲取讀取鎖嗎?能夠從新進入寫入鎖自己嗎?
• 能夠將寫入鎖在不容許其餘 writer 干涉的狀況降低級爲讀取鎖嗎?能夠優先於其餘等待的 reader 或 writer 將讀取鎖升級爲寫入鎖嗎?
雖然接口簡單,可是實現須要考慮的問題仍是很是多的,在下一篇鎖的實現中會再詳細介紹讀-寫鎖知識。
以上就是三個接口的一些入門介紹,下一篇會學習這些接口的相關實現類。