共享受限資源,解決共享資源競爭,Brian的同步規則

本文目錄

一、共享受限資源

二、解決共享資源競爭

2.1 使用synchronized關鍵字

2.2 顯示的使用Lock鎖

三、Brian的同步規則


一、共享受限資源

可以把單線程程序當作在問題域求解的單一實體,每次只能做一件事情。因爲只有一個實體,所以永遠不用擔心諸如“兩個實體試圖同時使用同一個資源”這樣的問題。比如,兩個人在同一個地方停車,兩個人同時走過一扇門,甚至是兩個人同時說話。

有了併發就可以同時做多件事情了,但是,兩個或多個線程彼此互相干涉的問題也就出現了。如果不防範這種衝突,就可能發生兩個線程同時試圖訪問同一個銀行賬戶,或向同一個打印機打印,改變同一個值等諸如此類的問題。

二、解決共享資源競爭

基本上所有的併發模式在解決線程衝突問題的時候,都是採用序列化訪問共享資源的方案。這意味着在給定時刻只允許一個任務訪問共享資源。通常這是通過在代碼前面加上一條鎖語句來實現的,這就使得在一段時間內只有一個任務可以運行這段代碼。因爲鎖語句產生了一種互相排斥的效果,所以這種機制常常稱爲互斥量(mutex)。

2.1 使用synchronized關鍵字

Java以提供 synchronized關鍵字的形式,爲防止資源衝突提供了內置支持。當任務要執行被synchronized關鍵字保護的代碼片段的時候,它將檢查鎖是否可用,然後獲取鎖,執行代碼,釋放鎖。

共享資源一般是以對象形式存在的內存片段,但也可以是文件、輸入/輸出端口,或者是打印機。要控制對共享資源的訪問,得先把它包裝進一個對象。然後把所有要訪問這個資源的方法標記爲 synchronized。如果某個任務處於一個對標記爲 synchronized的方法的調用中,那麼在這個線程從該方法返回之前,其他所有要調用類中任何標記爲 synchronized方法的線程都會被阻塞。

所有對象都自動含有單一的鎖(也稱爲監視器)。當在對象上調用其任意 synchronized方法的時候,此對象都被加鎖,這時該對象上的其他 synchronized方法只有等到前一個方法調用完畢並釋放了鎖之後才能被調用。

一個任務可以多次獲得對象的鎖。如果一個方法在同一個對象上調用了第二個方法,後者又調用了同一對象上的另一個方法,就會發生這種情況。JVM負責跟蹤對象被加鎖的次數。如果一個對象被解鎖(即鎖被完全釋放),其計數變爲0。在任務第一次給對象加鎖的時候,計數變爲1。每當這個相同的任務在這個對象上獲得鎖時,計數都會遞增。顯然,只有首先獲得了鎖的任務才能允許繼續獲取多個鎖。每當任務離開一個 synchronized方法,計數遞減,當計數爲零的時候,鎖被完全釋放,此時別的任務就可以使用此資源。針對每個類,也有一個鎖(作爲類的Class象的一部分);所以 synchronized static方法可以在類的範圍內防止對 static數據的併發訪問。

2.2 顯示的使用Lock鎖

Java SE5的java.util. concurrent類庫還包含有定義在 java util. concurrent. locks中的顯式的互斥機制。Lock對象必須被顯式地創建、鎖定和釋放。因此,它與內建的鎖形式相比,代碼缺乏優雅性。但是,對於解決某些類型的問題來說,它更加靈活。

儘管try- -finally所需的代碼比 synchronized關鍵字要多,但是這也代表了顯式的Lock對象的優點之一。如果在使用synchronized關鍵字時,某些事物失敗了,那麼就會拋出一個異常。但是你沒有機會去做任何清理工作,以維護系統使其處於良好狀態。有了顯式的Lock對象,你就可以使用 finally子句將系統維護在正確的狀態了。

大體上,當你使用 synchronized關鍵字時,需要寫的代碼量更少,並且用戶錯誤出現的可能性也會降低,因此通常只有在解決特殊問題時,才使用顯式的Lock對象。例如,用synchronized關鍵字不能嘗試着獲取鎖且最終獲取鎖會失敗,或者嘗試着獲取鎖一段時間,然後放棄它,要實現這些,你必須使用 concurrent類庫。

顯式的Lock對象在加鎖和釋放鎖方面,相對於內建的 synchronized鎖來說,還賦予了你更細粒度的控制力。這對於實現專有同步結構是很有用的,例如用於遍歷鏈接列表中的節點的節節傳遞的加鎖機制(也稱爲鎖耦合),這種遍歷代碼必須在釋放當前節點的鎖之前捕獲下一個節點的鎖。

三、Brian的同步規則

應該什麼時候同步呢?可以運用 Brian的同步規則:

如果你正在寫一個變量,它可能接下來將被另一個線程讀取,或者正在讀取一個上一次已經被另一個線程寫過的變量,那麼你必須使用同步,並且,讀寫線程都必須用相同的監視器鎖同步。

如果在你的類中有超過一個方法在處理臨界數據,那麼你必須同步所有相關的方法。如果只同步一個方法,那麼其他方法將會隨意地忽略這個對象鎖,並可以在無任何懲罰的情況下被調用。這是很重要的一點:每個訪問臨界共享資源的方法都必須被同步,否則它們就不會正確地工作。

注意: Java遞增不是原子性操作。

 

 

 

【參考資料】

Thinking in Java(Java編程思想)第四版

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