《Effective Java》 學習筆記 —— 併發
《Effective Java》第二版學習筆記之併發程式設計。
第66條 同步訪問共享的可變資料
* 關鍵字synchronized可以保證在同一時刻只有一個執行緒可以執行某個方法或程式碼塊。
* Java語音規範保證對一個變數的讀操作或者寫操作是原子性(atomic,注意 i++是非原子性的,64位的long型或double型變數的讀寫操作也是非原子性的),但並不保證一個執行緒寫入的值對另一個執行緒是可見的。
* 避免使用Thread.stop()方法,而是採用輪詢(poll)機制來終止一個執行緒。
* 如果只需要執行緒間的互動通訊,而不需要互斥,可以使用volatile關鍵字。
第67條 避免過度同步
* 為了避免活性失敗和安全性失敗,在一個被同步的方法或者程式碼塊中,永遠不要放棄對客戶端的控制(避免在同步區域呼叫不可信程式碼,否則容易造成死鎖、異常等問題)。
* 在同步程式碼塊內做盡可能少的事情。
第68條 executor 和 task 優先於執行緒
* Executor service 可以等待完成一項特殊的任務再繼續執行,也可以優雅的完成終止(利用awaitTerminalTermination方法),或可以在完成這些任務後逐個的獲取這些任務的結果(利用ExecutorCompletionService)等。
* 對於輕載的伺服器,Executors.newCachedThreadPool通常是個不錯的選擇;對於高負載的伺服器,最好使用Executors.newFixedThreadPool
* 儘量不要編寫自己的工作佇列和直接使用執行緒;現在關鍵的抽象不再是Thread,而是工作單元(任務,task)。
* Timer只有一個執行緒來執行任務,如果唯一的執行緒丟擲了未被捕獲的異常,任務就會終止。ScheduledThreadPoolExecutor 支援多個執行緒,並可以優雅的從丟擲未受檢異常的任務中恢復。
第69條 併發工具優先於wait和notify
* 幾乎沒有任何理由再使用wait和notify了。
但並不是說不需要掌握,如維護舊程式碼可能還是需要了解的,此種情況下務必確保是利用標準的模式從while迴圈內部呼叫wait。一般情況下優先使用notifyAll而不是notify。
* 使用高階工具:
執行器框架(Executor Framework)、併發集合(Concurrent Collection)、同步器(Synchronizer,如CountDownLatch、Semaphore、CyclicBarier和Exchanger等)。
第70條 執行緒安全性的文件化
* 執行緒安全的級別:
(1)不可變的(immutable):類的例項是不可變的(如String、Long、BigInteger等)。
(2)無條件的執行緒安全(unconditionally thread-safe):類的例項可變,但有著足夠的內部同步(如Random、ConcurrentHashMap。
(3)有條件的執行緒安全(conditionally thread-safe):除了有些方法為了安全的併發使用外部同步之外,與無條件的執行緒安全級別相同(如 Collections.synchronized 包裝返回的集合,它們的迭代器要求外部同步)。
(4)非執行緒安全(not thread-safe):需外部同步(如ArrayList、HashMap等)。
(5)執行緒對立(thread hostile):這個類不能安全的被多個執行緒併發的使用,即使所有的方法都被外部同步包圍。執行緒對立的根源通常在於,沒有同步的修改靜態資料。
* 為了避免拒絕服務攻擊,應當使用一個私有鎖物件(private lock object)來代替同步方法。私有鎖物件只能用在無條件的執行緒安全類上,特別適合於專門為繼承而設計的類。
第71條 慎用延遲初始化
* 正常的初始化優先於延遲初始化。
* 如果出於效能的考慮而需要對靜態域使用延遲初始化,就使用 lazy initiation holder class 模式:
1 private static class FieldHolder { 2 static final FieldType field = computeFieldValue(); 3 } 4 5 static FieldType getType() { 6 return FieldHolder.field; 7 }
* 如果出於效能的考慮而需要對例項域使用延遲初始化,就使用雙重校驗鎖:
1 // volatile 很重要,保證可見性 2 private volatile FieldType field; 3 4 FieldType getField() { 5 FieldType result = field; 6 if (result == null) { 7 synchronized(this) { 8 result = field; 9 if (result = null) { 10 field = result = computeFieldValue(); 11 } 12 } 13 } 14 return result; 15 }
第72條 不要依賴於執行緒排程器
* 不要讓應用程式依賴執行緒排程器,也不要依賴 Thread.yield 或者執行緒優先順序。這些設施僅僅對排程器作些暗示。
第73條 避免使用執行緒組
* 最好把執行緒組當作一個不成功的實驗,就當它們根本不存在一樣。
* 考慮使用執行緒池。