- Lock接口
Lock
實現提供了比使用synchronized
方法和語句可獲得的更廣泛的鎖定操作。此實現允許更靈活的結構,可以具有差別很大的屬性,可以支持多個相關的Condition
對象。
鎖是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問。一次只能有一個線程獲得鎖,對共享資源的所有訪問都需要首先獲得鎖。不過,某些鎖可能允許對共享資源并發訪問,如 ReadWriteLock
的讀取鎖。
synchronized
方法或語句的使用提供了對與每個對象相關的隱式監視器鎖的訪問,但卻強制所有鎖獲取和釋放均要出現在一個塊結構中:當獲取了多個鎖時,它們必須以相反的順序釋放,且必須在與所有鎖被獲取時相同的詞法范圍內釋放所有鎖。
雖然 synchronized
方法和語句的范圍機制使得使用監視器鎖編程方便了很多,而且還幫助避免了很多涉及到鎖的常見編程錯誤,但有時也需要以更為靈活的方式使用鎖。例如,某些遍歷并發訪問的數據結果的算法要求使用 "hand-over-hand" 或 "chain locking":獲取節點 A 的鎖,然后再獲取節點 B 的鎖,然后釋放 A 并獲取 C,然后釋放 B 并獲取 D,依此類推。Lock
接口的實現允許鎖在不同的作用范圍內獲取和釋放,并允許以任何順序獲取和釋放多個鎖,從而支持使用這種技術。
隨著靈活性的增加,也帶來了更多的責任。不使用塊結構鎖就失去了使用 synchronized
方法和語句時會出現的鎖自動釋放功能。在大多數情況下,應該使用以下語句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
鎖定和取消鎖定出現在不同作用范圍中時,必須謹慎地確保保持鎖定時所執行的所有代碼用 try-finally 或 try-catch 加以保護,以確保在必要時釋放鎖。
Lock
實現提供了使用 synchronized
方法和語句所沒有的其他功能,包括提供了一個非塊結構的獲取鎖嘗試 tryLock()
、一個獲取可中斷鎖的嘗試 lockInterruptibly()
和一個獲取超時失效鎖的嘗試tryLock(long, TimeUnit)
。
Lock
類還可以提供與隱式監視器鎖完全不同的行為和語義,如保證排序、非重入用法或死鎖檢測。如果某個實現提供了這樣特殊的語義,則該實現必須對這些語義加以記錄。
注意,Lock
實例只是普通的對象,其本身可以在 synchronized
語句中作為目標使用。獲取 Lock
實例的監視器鎖與調用該實例的任何 lock()
方法沒有特別的關系。為了避免混淆,建議除了在其自身的實現中之外,決不要以這種方式使用 Lock
實例。
除非另有說明,否則為任何參數傳遞 null
值都將導致拋出 NullPointerException
。
內存同步
所有 Lock
實現都必須 實施與內置監視器鎖提供的相同內存同步語義,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:
- 成功的
lock
操作與成功的 Lock 操作具有同樣的內存同步效應。 - 成功的
unlock
操作與成功的 Unlock 操作具有同樣的內存同步效應。
不成功的鎖定與取消鎖定操作以及重入鎖定/取消鎖定操作都不需要任何內存同步效果。
實現注意事項
三種形式的鎖獲取(可中斷、不可中斷和定時)在其性能特征、排序保證或其他實現質量上可能會有所不同。而且,對于給定的 Lock
類,可能沒有中斷正在進行的 鎖獲取的能力。因此,并不要求實現為所有三種形式的鎖獲取定義相同的保證或語義,也不要求其支持中斷正在進行的鎖獲取。實現必需清楚地對每個鎖定方法所提供的語義和保證進行記錄。還必須遵守此接口中定義的中斷語義,以便為鎖獲取中斷提供支持:完全支持中斷,或僅在進入方法時支持中斷。
由于中斷通常意味著取消,而通常又很少進行中斷檢查,因此,相對于普通方法返回而言,實現可能更喜歡響應某個中斷。即使出現在另一個操作后的中斷可能會釋放線程鎖時也是如此。實現應記錄此行為。
public interface Lock {
/**
* 獲取鎖。
* 如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在獲得鎖之前,該線程將一直處于休眠狀態。
*
* 實現注意事項:
* Lock 實現可能能夠檢測到鎖的錯誤使用,比如會導致死鎖的調用,在那種環境下還可能拋出一個 (unchecked) 異常。Lock 實現必須對環境和異常類型進行記錄。
*/
void lock();
/**
* 如果當前線程未被 中斷,則獲取鎖。
* 如果鎖可用,則獲取鎖,并立即返回。
*
* 如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下兩種情況之一以前,該線程將一直處于休眠狀態:
*
* 鎖由當前線程獲得;或者
* 其他某個線程中斷當前線程,并且支持對鎖獲取的中斷。
* 如果當前線程:
*
* 在進入此方法時已經設置了該線程的中斷狀態;或者
* 在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
* 則將拋出 InterruptedException,并清除當前線程的已中斷狀態。
* 實現注意事項
*
* 在某些實現中可能無法中斷鎖獲取,即使可能,該操作的開銷也很大。程序員應該知道可能會發生這種情況。在這種情況下,該實現應該對此進行記錄。
*
* 相對于普通方法返回而言,實現可能更喜歡響應某個中斷。
*
* Lock 實現可能可以檢測鎖的錯誤用法,例如,某個調用可能導致死鎖,在特定的環境中可能拋出(未經檢查的)異常。該 Lock 實現必須對環境和異常類型進行記錄。
*/
void lockInterruptibly() throws InterruptedException;
/**
* 僅在調用時鎖為空閑狀態才獲取該鎖。
* 如果鎖可用,則獲取鎖,并立即返回值 true。如果鎖不可用,則此方法將立即返回值 false。
*
* 此方法的典型使用語句如下:
*
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // manipulate protected state
* } finally {
* lock.unlock();
* }
* } else {
* // perform alternative actions
* }
*
* 此用法可確保如果獲取了鎖,則會釋放鎖,如果未獲取鎖,則不會試圖將其釋放。
* 返回:
* 如果獲取了鎖,則返回 true;否則返回 false。
*/
boolean tryLock();
/**
*如果鎖在給定的等待時間內空閑,并且當前線程未被 中斷,則獲取鎖。
* 如果鎖可用,則此方法將立即返回值 true。如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下三種情況之一前,該線程將一直處于休眠狀態:
*
* 鎖由當前線程獲得;或者
* 其他某個線程中斷當前線程,并且支持對鎖獲取的中斷;或者
* 已超過指定的等待時間
* 如果獲得了鎖,則返回值 true。
*
* 如果當前線程:
*
* 在進入此方法時已經設置了該線程的中斷狀態;或者
* 在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
* 則將拋出 InterruptedException,并會清除當前線程的已中斷狀態。
* 如果超過了指定的等待時間,則將返回值 false。如果 time 小于等于 0,該方法將完全不等待。
*
* 實現注意事項
*
* 在某些實現中可能無法中斷鎖獲取,即使可能,該操作的開銷也很大。程序員應該知道可能會發生這種情況。在這種情況下,該實現應該對此進行記錄。
*
* 相對于普通方法返回而言,實現可能更喜歡響應某個中斷,或者報告出現超時情況。
*
* Lock 實現可能可以檢測鎖的錯誤用法,例如,某個調用可能導致死鎖,在特定的環境中可能拋出(未經檢查的)異常。該 Lock 實現必須對環境和異常類型進行記錄。
*
* 參數:
* time - 等待鎖的最長時間
* unit - time 參數的時間單位
* 返回:
* 如果獲得了鎖,則返回 true;如果在獲取鎖前超過了等待時間,則返回 false
* 拋出:
* InterruptedException - 如果在獲取鎖時,當前線程被中斷(并且支持對鎖獲取的中斷)
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
*釋放鎖。
* 實現注意事項
*
* Lock 實現通常對哪個線程可以釋放鎖施加了限制(通常只有鎖的保持者可以釋放它),如果違背了這個限制,可能會拋出(未經檢查的)異常。該 Lock 實現必須對所有限制和異常類型進行記錄。
*/
void unlock();
/**
*返回綁定到此 Lock 實例的新 Condition 實例。
* 在等待條件前,鎖必須由當前線程保持。調用 Condition.await() 將在等待前以原子方式釋放鎖,并在等待返回前重新獲取鎖。
*
* 實現注意事項
*
* Condition 實例的具體操作依賴于 Lock 實現,并且該實現必須對此加以記錄。
*
* 返回:
* 用于此 Lock 實例的新 Condition 實例
* 拋出:
* UnsupportedOperationException - 如果此 Lock 實現不支持條件
*/
Condition newCondition();
}
-
ReadWriteLock 接口
ReadWriteLock 維護了一對相關的鎖,一個用于只讀操作,另一個用于寫入操作。只要沒有 writer,讀取鎖可以由多個 reader 線程同時保持。寫入鎖是獨占的。
???所有ReadWriteLock 實現都必須保證 writeLock操作的內存同步效果也要保持與相關readLock的聯系。也就是說,成功獲取讀鎖的線程會看到寫入鎖之前版本所做的所有更新。
???與互斥鎖相比,讀-寫鎖允許對共享數據進行更高級別的并發訪問。雖然一次只有一個線程(writer 線程)可以修改共享數據,但在許多情況下,任何數量的線程可以同時讀取共享數據(reader 線程),讀-寫鎖利用了這一點。從理論上講,與互斥鎖相比,使用讀-寫鎖所允許的并發性增強將帶來更大的性能提高。在實踐中,只有在多處理器上并且只在訪問模式適用于共享數據時,才能完全實現并發性增強。
???與互斥鎖相比,使用讀-寫鎖能否提升性能則取決于讀寫操作期間讀取數據相對于修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操作的線程數。例如,某個最初用數據填充并且之后不經常對其進行修改的 collection,因為經常對其進行搜索(比如搜索某種目錄),所以這樣的 collection 是使用讀-寫鎖的理想候選者。但是,如果數據更新變得頻繁,數據在大部分時間都被獨占鎖,這時,就算存在并發性增強,也是微不足道的。更進一步地說,如果讀取操作所用時間太短,則讀-寫鎖實現(它本身就比互斥鎖復雜)的開銷將成為主要的執行成本,在許多讀-寫鎖實現仍然通過一小段代碼將所有線程序列化時更是如此。最終,只有通過分析和測量,才能確定應用程序是否適合使用讀-寫鎖。
盡管讀-寫鎖的基本操作是直截了當的,但實現仍然必須作出許多決策,這些決策可能會影響給定應用程序中讀-寫鎖的效果。這些策略的例子包括:- 在 writer 釋放寫入鎖時,reader 和 writer 都處于等待狀態,在這時要確定是授予讀取鎖還是授予寫入鎖。Writer 優先比較普遍,因為預期寫入所需的時間較短并且不那么頻繁。Reader 優先不太普遍,因為如果 reader 正如預期的那樣頻繁和持久,那么它將導致對于寫入操作來說較長的時延。公平或者“按次序”實現也是有可能的。
- 在 reader 處于活動狀態而 writer 處于等待狀態時,確定是否向請求讀取鎖的 reader 授予讀取鎖。Reader 優先會無限期地延遲 writer,而 writer 優先會減少可能的并發。
- 確定是否重新進入鎖:可以使用帶有寫入鎖的線程重新獲取它嗎?可以在保持寫入鎖的同時獲取讀取鎖嗎?可以重新進入寫入鎖本身嗎?
- 可以將寫入鎖在不允許其他 writer 干涉的情況下降級為讀取鎖嗎?可以優先于其他等待的 reader 或 writer 將讀取鎖升級為寫入鎖嗎?
public interface ReadWriteLock { /** 返回用于讀的鎖 */ Lock readLock(); /**返回用于寫的鎖 */ Lock writeLock(); }
Condition接口
Condition
將Object
監視器方法(wait
、notify
和notifyAll
)分解成截然不同的對象,以便通過將這些對象與任意Lock
實現組合使用,為每個對象提供多個等待 set(wait-set)。其中,Lock
替代了synchronized
方法和語句的使用,Condition
替代了 Object 監視器方法的使用。
條件(也稱為條件隊列 或條件變量)為線程提供了一個含義,以便在某個狀態條件現在可能為 true 的另一個線程通知它之前,一直掛起該線程(即讓其“等待”)。因為訪問此共享狀態信息發生在不同的線程中,所以它必須受保護,因此要將某種形式的鎖與該條件相關聯。等待提供一個條件的主要屬性是:以原子方式 釋放相關的鎖,并掛起當前線程,就像 Object.wait
做的那樣。
Condition
實例實質上被綁定到一個鎖上。要為特定 Lock
實例獲得 Condition
實例,請使用其 newCondition()
方法。
作為一個示例,假定有一個綁定的緩沖區,它支持 put
和 take
方法。如果試圖在空的緩沖區上執行 take
操作,則在某一個項變得可用之前,線程將一直阻塞;如果試圖在滿的緩沖區上執行 put
操作,則在有空間變得可用之前,線程將一直阻塞。我們喜歡在單獨的等待 set 中保存 put
線程和 take
線程,這樣就可以在緩沖區中的項或空間變得可用時利用最佳規劃,一次只通知一個線程。可以使用兩個 Condition
實例來做到這一點。
class BoundedBuffer {
**final Lock lock = new ReentrantLock();**
final Condition notFull = **lock.newCondition();**
final Condition notEmpty = **lock.newCondition();**
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
**lock.lock();
try {**
while (count == items.length)
**notFull.await();**
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
**notEmpty.signal();**
**} finally {
lock.unlock();
}**
}
public Object take() throws InterruptedException {
**lock.lock();
try {**
while (count == 0)
**notEmpty.await();**
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
**notFull.signal();**
return x;
**} finally {
lock.unlock();
}**
}
}
( ArrayBlockingQueue
類提供了這項功能,因此沒有理由去實現這個示例類。)
Condition
實現可以提供不同于 Object
監視器方法的行為和語義,比如受保證的通知排序,或者在執行通知時不需要保持一個鎖。如果某個實現提供了這樣特殊的語義,則該實現必須記錄這些語義。
注意,Condition
實例只是一些普通的對象,它們自身可以用作 synchronized
語句中的目標,并且可以調用自己的 wait
和 notification
監視器方法。獲取 Condition
實例的監視器鎖或者使用其監視器方法,與獲取和該 Condition
相關的 Lock
或使用其 waiting
和 signalling
方法沒有什么特定的關系。為了避免混淆,建議除了在其自身的實現中之外,切勿以這種方式使用 Condition
實例。
除非另行說明,否則為任何參數傳遞 null
值將導致拋出 NullPointerException
。
實現注意事項
在等待 Condition
時,允許發生“虛假喚醒”,這通常作為對基礎平臺語義的讓步。對于大多數應用程序,這帶來的實際影響很小,因為 Condition
應該總是在一個循環中被等待,并測試正被等待的狀態聲明。某個實現可以隨意移除可能的虛假喚醒,但建議應用程序程序員總是假定這些虛假喚醒可能發生,因此總是在一個循環中等待。
三種形式的條件等待(可中斷、不可中斷和超時)在一些平臺上的實現以及它們的性能特征可能會有所不同。尤其是它可能很難提供這些特性和維護特定語義,比如排序保證。更進一步地說,中斷線程實際掛起的能力在所有平臺上并不是總是可行的。
因此,并不要求某個實現為所有三種形式的等待定義完全相同的保證或語義,也不要求其支持中斷線程的實際掛起。
要求實現清楚地記錄每個等待方法提供的語義和保證,在某個實現不支持中斷線程的掛起時,它必須遵從此接口中定義的中斷語義。
由于中斷通常意味著取消,而又通常很少進行中斷檢查,因此實現可以先于普通方法的返回來對中斷進行響應。即使出現在另一個操作后的中斷可能會釋放線程鎖時也是如此。實現應記錄此行為。
public interface Condition {
// 造成當前線程在接到信號或被[中斷](http://tool.oschina.net/uploads/apidocs/jdk- zh/java/lang/Thread.html#interrupt())之前一直處于等待狀態。
void await() throws InterruptedException;
// 造成當前線程在接到信號之前一直處于等待狀態。
void awaitUninterruptibly();
// 造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處于等待狀態。
long awaitNanos(long nanosTimeout) throws InterruptedException;
//造成當前線程在接到信號之前一直處于等待狀態。
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 造成當前線程在接到信號、被中斷或到達指定最后期限之前一直處于等待狀態。
boolean awaitUntil(Date deadline) throws InterruptedException;
//喚醒一個等待在Condition上的線程
void signal();
//喚醒所有等待在這個Condition上的線程
void signalAll();
}