線程的第二節課,開始同步方面的知識,首先要了解的就是鎖的概念,以前只知道synchronized關鍵字可以同步線程,現在深入瞭解一下。
(1)瑣對象
有兩種機制防止代碼塊受併發訪問的干擾。Java語言提供一個 synchronized 關鍵字到這一目的,並且 Java SE 5.0 引入了 ReentrantLock 類。synchronized 關鍵字自動提供一個鎖以及相關的“ 條件”, 對於大多數需要顯式鎖的情況, 這是很便利的。但是, 我們相信在我們相信在讀者分別閱讀了鎖和條件的內容之後, 理解 synchronized 關鍵字是很輕鬆的事情。
用 ReentrantLock 保護代碼塊的基本結構如
myLock.lockO; // a ReentrantLock object
try{
critical section
}finally{
myLock.unlockO; }
這一結構確保任何時刻只有一個線程進人臨界區。一旦一個線程封鎖了鎖對象, 其他任何線程都無法通過 lock 語句。當其他線程調用 lock 時,它們被阻塞,直到第一個線程釋放鎖對象。把解鎖操作括在 finally 子句之內是至關重要的。如果在臨界區的代碼拋出異常,鎖必須被釋放。否則, 其他線程將永遠阻塞。
(2) 條件對象
通常, 線程進人臨界區,卻發現在某一條件滿足之後它才能執行。要使用一個條件對象來管理那些已經獲得了一個鎖但是卻不能做有用工作的線程。
一個鎖對象可以有一個或多個相關的條件對象。你可以用 newCondition 方法獲得一個條件對象。習慣上給每一個條件對象命名爲可以反映它所表達的條件的名字。例如,在此設置一個條件對象來表達“ 餘額充足” 條件
class Bank{
private Condition sufficientFunds;
public BankO{
sufficientFunds = bankLock.newCondition();
}
如果 transfer 方法發現餘額不足,它調用sufficientFunds.await();
等待獲得鎖的線程和調用 await 方法的線程存在本質上的不同。一旦一個線程調用 await方法, 它進人該條件的等待集。當鎖可用時,該線程不能馬上解除阻塞。相反,它處於阻塞狀態,直到另一個線程調用同一條件上的 signalAll 方法時爲止。
至關重要的是最終需要某個其他線程調用 signalAll 方法。當一個線程調用 await 時,它沒有辦法重新激活自身。它寄希望於其他線程。如果沒有其他線程來重新激活等待的線程,它就永遠不再運行了。這將導致令人不快的死鎖( deadlock) 現象。如果所有其他線程被阻塞, 最後一個活動線程在解除其他線程的阻塞狀態之前就調用 await 方法, 那麼它也被阻塞。沒有任何線程可以解除其他線程的阻塞,那麼該程序就掛起了。
應該何時調用 signalAll 呢? 經驗上講, 在對象的狀態有利於等待線程的方向改變時調用signalAll。例如, 當一個賬戶餘額發生改變時,等待的線程會應該有機會檢查餘額。在例子中, 當完成了轉賬時, 調用 signalAll 方法。
另一個方法 signal, 則是隨機解除等待集中某個線程的阻塞狀態。這比解除所有線程阻塞更加有效,但也存在危險。如果隨機選擇的線程發現自己仍然不能運行, 那麼它再次被阻塞。如果沒有其他線程再次調用 signal, 那麼系統就死鎖了。
介紹瞭如何使用 Lock 和 Condition 對象。在進一步深人之前,總結一下有關鎖和條件的關鍵之處:
•鎖用來保護代碼片段, 任何時刻只能有一個線程執行被保護的代碼。
•鎖可以管理試圖進入被保護代碼段的線程。
•鎖可以擁有一個或多個相關的條件對象。
•每個條件對象管理那些已經進入被保護的代碼段但還不能運行的線程。
(3) synchronized 關鍵字
Lock 和 Condition 接口爲程序設計人員提供了高度的鎖定控制。然而,大多數情況下,並不需要那樣的控制,並且可以使用一種嵌人到 Java語言內部的機制。從 1.0 版開始,Java中的每一個對象都有一個內部鎖。如果一個方法用 synchronized關鍵字聲明,那麼對象的將保護整個方法。也就是說,要調用該方法,線程必須獲得內部的對象。
public synchronized void methodO{
method body
}
//等價於
public void methodQ{
this.intrinsidock.1ock();
try{
method body
}finally { this.intrinsicLock.unlockO; }
}
內部對象鎖只有一個相關條件。wait 方法添加一個線程到等待集中,notifyAU /notify方法解除等待線程的阻塞狀態。 使用 synchronized 關鍵字來編寫代碼要簡潔得多。當然,要理解這一代碼,你必須瞭解每一個對象有一個內部鎖, 並且該鎖有一個內部條件。由鎖來管理那些試圖進入synchronized 方法的線程,由條件來管理那些調用 wait 的線程。
內部鎖和條件存在一些侷限。包括:
•不能中斷一個正在試圖獲得鎖的線程。
•試圖獲得鎖時不能設定超時
•每個鎖僅有單一的條件, 可能是不夠
在代碼中應該使用哪一種? Lock 和 Condition 對象還是同步方法?下面是一些建議:
•最好既不使用 Lock/Condition 也不使用 synchronized 關鍵字。在許多情況下你可以使用 java.util.concurrent 包中的一種機制,它會爲你處理所有的加鎖。(後面再看)
•如果 synchronized 關鍵字適合你的程序, 那麼請儘量使用它,這樣可以減少編寫的代碼數量,減少出錯的機率。
•如果特別需要 Lock/Condition 結構提供的獨有特性時才使用 Lock/Condition。
public class Dame2 {
public synchronized void test1() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public synchronized void test2() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
Dame2 myt2 = new Dame2();
new Thread(() -> myt2.test2(), "test2").start();
new Thread(() -> myt2.test1(), "test1").start();
}
}