java核心技術卷I-同步(一)

同步

在大多數實際的多線程應用中, 兩個或兩個以上的線程需要共享對同一數據的存取。如果兩個線程存取相同的對象, 並且每一個線程都調用了一個修改該對象狀態的方法, 可以想象, 線程彼此踩了對方的腳。根據各線程訪問數據的次序,可能會產生錯誤的對象。這樣一個情況通常稱爲競爭條件(race condition)。

競爭條件詳解

java允許多線程併發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查),將會導致數據不準確,相互之間產生衝突。

鎖對象

有兩種機制防止代碼塊受併發訪問的干擾。Java語言提供一個 synchronized 關鍵字達
到這一目的,並且 Java SE 5.0 引入了 ReentrantLock 類。synchronized 關鍵字自動提供一個鎖以及相關的“ 條件”, 對於大多數需要顯式鎖的情況, 這是很便利的。但是, 我們相信在讀者分別閱讀了鎖和條件的內容之後, 理解 synchronized 關鍵字是很輕鬆的事情。java.util.concurrent 框架爲這些基礎機制提供獨立的類

private Lock bankLock = new ReentrantLock();// ReentrantLock implements the Lock interface
myLock.lock(); // a ReentrantLock object
try{
	critical section
}
finally
{
	myLock.unlock();// make sure the lock is unlocked even if an exception is thrown
}

這一結構確保任何時刻只有一個線程進人臨界區。一旦一個線程封鎖了鎖對象, 其他任何線程都無法通過 lock 語句。當其他線程調用 lock 時,它們被阻塞,直到第一個線程釋放鎖對象。
把解鎖操作括在 finally 子句之內是至關重要的。如果在臨界區的代碼拋出異常,鎖必須被釋放。否則, 其他線程將永遠阻塞。
假定一個線程調用 方法A, 在執行結束前被剝奪了運行權。假定第二個線程也調用方法A, 由於第二個線程不能獲得鎖, 將在調用 lock 方法時被阻塞。它必須等待第一個線程完成 方法A的執行之後才能再度被激活。當第一個線程釋放鎖時, 那麼第二個線程才能開始運行。
鎖是可重入的, 因爲線程可以重複地獲得已經持有的鎖。鎖保持一個持有計數(hold count) 來跟蹤對lock方法的嵌套調用。線程在每一次調用 lock 都要調用 unlock 來釋放鎖。由於這一特性, 被一個鎖保護的代碼可以調用另一個使用相同的鎖的方法。
通常, 可能想要保護需若干個操作來更新或檢查共享對象的代碼塊。要確保這些操作完成後, 另一個線程才能使用相同對象。
要留心臨界區中的代碼,不要因爲異常的拋出而跳出臨界區。如果在臨界區代碼結束之前拋出了異常,finally 子句將釋放鎖,但會使對象可能處於一種受損狀態。

條件對象

通常, 線程進人臨界區,卻發現在某一條件滿足之後它才能執行。要使用一個條件對象來管理那些已經獲得了一個鎖但是卻不能做有用工作的線程。
因爲不滿足條件,無法繼續執行,線程獲得了對 bankLock 的排它性訪問,因此別的線程沒有進行存款操作的機會。這就是爲什麼我們需要條件對象的原因。
一個鎖對象可以有一個或多個相關的條件對象。你可以用 newCondition 方法獲得一個條
件對象。習慣上給每一個條件對象命名爲可以反映它所表達的條件的名字。例如

class Bank
{
	private Condition sufficientFunds;
	public Bank()
	{
		sufficientFunds = bankLock.newConditionO;
	}
}

不滿足條件是調用:
sufficientFunds.await();
當前線程現在被阻塞了,並放棄了鎖。它進人該條件的等待集。當鎖可用時,該線程不能馬上解除阻塞。相反,它處於阻塞狀態,直到另一個線程調用同一條件上的 signalAll 方法時爲止,如

sufficientFunds,signalAll();

這一調用重新激活因爲這一條件而等待的所有線程。當這些線程從等待集當中移出時,它們再次成爲可運行的,調度器將再次激活它們。同時, 它們將試圖重新進人該對象。一旦鎖成爲可用的,它們中的某個將從 await 調用返回, 獲得該鎖並從被阻塞的地方繼續執行。
至關重要的是最終需要某個其他線程調用 signalAll 方法。當一個線程調用 await 時,它沒有辦法重新激活自身。它寄希望於其他線程。如果沒有其他線程來重新激活等待的線程,它就永遠不再運行了。這將導致令人不快的死鎖( deadlock) 現象。如果所有其他線程被阻塞, 最後一個活動線程在解除其他線程的阻塞狀態之前就調用 await 方法, 那麼它也被阻塞。沒有任何線程可以解除其他線程的阻塞,那麼該程序就掛起了。
應該何時調用 signalAll 呢? 經驗上講, 在對象的狀態有利於等待線程的方向改變時調用signalAll。
注意調用 signalAll 不會立即激活一個等待線程。它僅僅解除等待線程的阻塞, 以便這些線程可以在當前線程退出同步方法之後,通過競爭實現對對象的訪問。
另一個方法 signal, 則是隨機解除等待集中某個線程的阻塞狀態。這比解除所有線程的阻塞更加有效,但也存在危險。如果隨機選擇的線程發現自己仍然不能運行, 那麼它再次被阻塞。如果沒有其他線程再次調用 signal, 那麼系統就死鎖了。
當一個線程擁有某個條件的鎖時, 它僅僅可以在該條件上調用 await、signalAll 或
signal 方法。

synchronized關鍵字

鎖的特點和作用

鎖用來保護代碼片段, 任何時刻只能有一個線程執行被保護的代碼。
鎖可以管理試圖進入被保護代碼段的線程。
鎖可以擁有一個或多個相關的條件對象。
每個條件對象管理那些已經進入被保護的代碼段但還不能運行的線程。

Lock 和 Condition 接口爲程序設計人員提供了高度的鎖定控制。然而,大多數情況下,並不需要那樣的控制,並且可以使用一種嵌人到 Java語言內部的機制。從 1.0 版開始,Java中的每一個對象都有一個內部鎖。如果一個方法用 synchronized關鍵字聲明,那麼對象的鎖將保護整個方法。也就是說,要調用該方法,線程必須獲得內部的對象鎖。

public synchronized void method()
{
	method body
}

等價於

public void method()
{
	this.intrinsidock.lock();
	try
	{
		method body
	}
	finally { this.intrinsicLock.unlock(); }
}

內部對象鎖只有一個相關條件。wait 方法添加一個線程到等待集中,notifyAll /notify方法解除等待線程的阻塞狀態。調用 wait 或 notityAll 等價於

intrinsicCondition.await();
intrinsicCondition.signalAIl();

wait、notifyAll 以及 notify 方法是 Object 類的 final 方法。Condition 方法必須被命名爲 await、signalAll 和 signal 以便它們不會與那些方法發生衝突

class Bank
{
	private double[] accounts;
	public synchronized void transfer(int from,int to, int amount) throws  InterruptedException
	{
		while (accounts[from] < amount)
			wait(); // wait on intrinsic object lock's single condition
			accounts[from] -= amount ;
			accounts[to] += amount ;
			notifyAll();// notify all threads waiting on the condition
	}
	public synchronized double getTotalBalance() { . . . }
}

可以看到, 使用 synchronized 關鍵字來編寫代碼要簡潔得多。當然,要理解這一代碼,你必須瞭解每一個對象有一個內部鎖, 並且該鎖有一個內部條件。由鎖來管理那些試圖進入synchronized 方法的線程,由條件來管理那些調用 wait 的線程。
將靜態方法聲明爲 synchronized 也是合法的。如果調用這種方法,該方法獲得相關的類對象的內部鎖。
內部鎖和條件存在一些侷限

不能中斷一個正在試圖獲得鎖的線程。
試圖獲得鎖時不能設定超時。
每個鎖僅有單一的條件, 可能是不夠的

Lock 和 Condition 對象還是同步方法,選擇的建議

最好既不使用 Lock/Condition 也不使用 synchronized 關鍵字。在許多情況下你可以使用
java.util.concurrent 包中的一種機制,它會爲你處理所有的加鎖。 如果 synchronized 關鍵字適合你的程序,那麼請儘量使用它,這樣可以減少編寫的代碼數量,減少出錯的機率。
如果特別需要 Lock/Condition 結構提供的獨有特性時,才使用Lock/Condition。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章