DougLea:《Concurrent Programming in Java:Design Principles and Patterns》
1、In Action
(1)執行的可能路徑
Java代碼會變成字節碼指令。
對於指令系列中有N個指令和T個線程,沒有循環或條件分支的簡單情況,總的執行路徑數量等於(NT)! / (N!)的T次冪。
根據java內存模型,32位值的賦值操作是不可中斷的。如 int a = 2;
根據JVM規約,64位值的賦值需要兩次32位賦值。
框架——每個方法調用都需要一個框架。該框架包括返回地址、傳入方法的參數,以及方法中定義的本地變量。這是定義一個調用堆棧的標準技術。現代編程語言用來實現基本函數調用和遞歸調用。
本地變量——方法作用範圍內定義的每個變量。所有非靜態方法至少有一個變量this,代表當前對象,即接收導致方法調用的(當前線程內)大多數最新消息的對象。
運算對象棧——java虛擬機中的許多指令都有參數。運算對象棧是放置參數的地方。
操作對象都處理對於方法而言是本地的信息。故多個線程之間並無衝突。
理解線程之間如何相互干涉的,並不一定要精通字節碼。有必要儘量理解內存模型,明白什麼是安全的,什麼是不安全的。
必須要知道:
a、什麼地方有共享對象/值;
b、哪些代碼會導致併發讀/寫問題;
c、如何防止這種併發問題發生。
(2)Executor框架
Executor框架支持利用線程池進行復雜的執行。Executor框架將線程放到池中,自動調整其大小,並在必要時重建線程。它支持future,一種通用的併發編程構造。Executor能與實現了Runnable的類協同工作,也能與實現了Callable接口的類協同工作。Callback看來就像是Runnable,但它能返回一個結果。
(3)
2、TIPS
(1)要保持併發系統整潔,應該將線程管理代碼約束於少數幾處控制良好的地方。
爲每個職責創建單獨的類。
(2)非鎖定的解決方案
java5虛擬機利用現代處理器支持可靠、非鎖定更新的設計優點。
如java5有一系列的新類,如AtomicBoolean、AtomicInteger、AtomicReference等。可以使用非鎖定的手段。
現代處理器有比較交換(CAS)操作,這種操作類似於數據庫中的樂觀鎖定,而其同步版本則類似於保守鎖定。
關鍵字synchronized總是要求上鎖,即使第二個線程並不更新同一值時也是如此。儘管這種固有鎖的性能一直在提升,但代價仍然昂貴。
非上鎖的版本假定多個線程通常並不頻繁修改同一個值。
CAS的操作是原子的。邏輯上是這樣:當某個方法試圖更新一個共享變量,CAS操作就會驗證要賦值的變量是否保有上一次的已知值。若是,就修改變量值。若不是,則不會碰變量,因爲另一個線程正在試圖更新變量值。要更新數據的方法(通過CAS操作)查看是否修改並持續嘗試。
(3)非線程安全類
a、數據庫連接
b、java.util中的容器
c、servlet
線程安全的集合:java.util.concurrent中的集合,如ConcurrentHashMap。
(4)出現錯誤時,有兩種解決方法
a、基於客戶代碼的鎖定
b、基於服務端的鎖定(建議)——修改服務端代碼解決問題,同時也修改了客戶代碼。若無法修改服務端代碼,可以使用適配器模式修改API,添加鎖定。
更好的方法是使用線程安全的集合和擴展接口。
(5)死鎖
死鎖的發生需要4個條件:
a、互斥:當多個線程需要使用同一資源,而此資源無法在同一時間爲多個線程所用;
b、上鎖及等待:當某個線程獲取一個資源,在獲取到其他全部所需資源並完成其工作之前,不會釋放這個資源;
c、無搶先機制:線程無法從其他線程處奪取資源。一個線程持有資源時,其它線程獲得這個資源的唯一手段就是等待該線程釋放資源;
d、循環等待
這4個條件都是死鎖必需的。只要其中一個不滿足,死鎖就不會發生。
避免死鎖的一種策略是規避互斥條件。可以使用允許同時使用的資源,如AtomicInteger;
線程1同時需要資源1和資源2、線程2同時需要資源2和資源1,只要強制線程1和線程2以同樣次序分配資源,循環等待就不會發生。
有許多避免死鎖的方法,有些會導致飢餓,另外一些會導致對CPU能力的大量耗費和降低響應率。
將解決方案中與線程相關的部分分割出來,再加以調整和試驗。
小心記錄在何種條件下測試失敗。
測試線程代碼的工具支持:
IBM提供ConTest的工具,它能對類進行裝置,令非線程安全代碼更有可能失敗。
(6)
3、PS
(1)系統在什麼地方耗費時間
a、I/O——使用套接字、連接到數據庫、等待虛擬內存交換等;
b、處理器——數值計算、正則表達式處理、垃圾回收等。
若代碼運行速度主要和處理器相關,增加處理器硬件就能提升吞吐量。CPU運算週期是有上限的,只是增加線程的話並不會提升受處理器限制的代碼的速度。
若吞吐量與IO有關,則併發編程能提升運行效率。當系統的某個部分在等待IO,另一部分就可以利用等待的時間處理其他事,從而有效利用了CPU能力。
IO操作不耗費處理器能力。
(2)