Java併發程式設計實踐——讀書筆記(一)
Java併發程式設計實踐——讀書筆記(一)
《Java Concurrency in Practice》第一部分的閱讀總結
關鍵字:併發、鎖、執行緒安全、共享物件、併發容器、訊號量
目錄
第一章 介紹
1.1 併發的簡短歷史
1.2 執行緒的優點
1.3 執行緒的風險
1.4 執行緒無處不在
第二章 執行緒安全
2.1 什麼是執行緒安全性
2.2 原子性
2.3 鎖
2.4 用鎖來保護狀態
2.5 活躍度與效能
第三章 共享物件
3.1 可見性
3.2 釋出和逸出
3.3 執行緒封閉
3.4 不可變性
3.5 安全釋出
第四章 組合物件
4.1 設計執行緒安全的類
4.2 例項限制
4.3 委託執行緒安全
4.4 向已有的執行緒安全類新增功能
4.5 同步策略的文件化
第五章 構建塊
5.1 同步容器
5.2 併發容器
5.3 阻塞佇列
5.4 阻塞和可中斷的方法
5.5 Synchronizer
5.6 為計算結果建立高效、可伸縮的快取記憶體
第一章
-
執行緒的風險
-
競爭場景(race condition)
多個執行緒併發地對共享資料進行操作
-
活躍度失敗(liveness failure)(程式無法繼續執行或退出)
如果安全意味著”什麼壞事都沒有發生“,那麼活躍度關注的則是”好事最終發生了“
-
效能危險(performance)
...,執行緒仍然會給執行時帶來一定程度的開銷。上下文切換(Context switches) ,...,這在多個執行緒組成的應用程式中是很頻繁的,並且帶來巨大的系統開銷。...,CPU的時間會花費在對執行緒的排程上而不是在執行上。
-
第二章
-
執行緒的安全性
正確性意味著一個類與它的規約保持一致。良好的規約定義了用於強制物件狀態的不變約束(invariants)以及描述操作影響的後驗條件(postcoditions)
一個類是執行緒安全的,是指在被多個執行緒訪問時,類可以持續進行正確的行為
-
原子性
假設有操作A和B,如果從執行A的執行緒的角度看,當其他執行緒執行B的時候,耀目B全部執行完成,要麼一點都沒執行,這樣A和B互為原子操作
-
Java.uitl.concurrent.atomic包中包括了原子變數類(atomic variable),這些類實現了數字和物件引用的原子轉換(該物件的操作都是原子的)
- AtomicLong(基本型別對應的原子物件)
- AtomicReference<V>(一些物件引用對應的原子物件)
-
複合操作——”讀-改-寫“和”檢查再執行“
- 這是兩類常見的看似原子,但是卻不是原子的操作。讀改寫對應賦值語句,檢查再執行對應條件判斷
-
-
鎖
-
內部鎖
- synchronized(鎖物件的引用,鎖程式碼塊)
- 方法級別的synchronized,鎖物件是方法的呼叫者。靜態方法的synchronized,鎖物件是對應的呼叫者物件的class物件
- 內部鎖是一種互斥鎖,至多隻有一個執行緒可以擁有鎖,但是內部鎖是可重入的。
-
重進入(Reentrancy)
執行緒在試圖獲得它自己佔優的鎖的時候,如果請求成功,那麼該鎖是可重入的。
-
-
鎖對狀態的保護
對於每個可被多個執行緒訪問的可變狀態變數,如果所有訪問它的執行緒在執行時都佔有同一個鎖,這種情況下,我們稱這個變數是由這個鎖保護的
- 內部鎖只能確保一件事,一個執行緒獲得物件的鎖之後,將阻塞其他執行緒獲得這個鎖
- Vector 和Hashtable
- 通過使用物件的內部鎖(synchronized關鍵字)來封裝所有的可變狀態
對於每一個涉及多個變數的不變約束,需要同一個鎖保護其他所有的變數
第三章
-
可見性(defined)
在沒有同步的情況下,編譯器、處理器,執行時安排操作的執行順序可能完全出入意料。在沒有進行適當同步的多執行緒程式中,嘗試推斷那些”必然"發生在記憶體中的動作,你總會判斷錯誤。
可見性保證了,記憶體中的值是已定義的(也就是能夠判斷出來的),可能聽著還是有些抽象。舉個例子,多個執行緒對一個64位的long long變數進行值的修改,那麼可能執行緒A正在修改變數的高32位地址,而執行緒B在修改變數的低32位地址,這樣最後的產生的值就是undefined,也就是無法判斷的。
-
可見性比並發的要求弱, 意味著資料可能過期
-
Volatile
- 輕量級的同步機制
- 只保證了對應變數引用的記憶體是“可見的”,不保證資料同步 。
- 從主存讀,並且寫入記憶體。(不存在cpu暫存器中,效能損失)
-
鎖不僅僅是關於同步與互斥的,也是關於記憶體可見的。為了保證所有執行緒都能夠看到共享的、可變變數的最新值,讀取和寫入執行緒必須使用公共的鎖進行同步
-
-
物件的釋出與逸出(publishing & escape)
- 釋出:使它能夠被當前範圍之外的程式碼所使用
- 將物件的引用儲存到公共靜態域中
- 從非私有方法中返回引用
- 釋出一個物件,同樣也釋出了該物件所有非私有域所引用的物件
- 逸出:未經計劃的釋出
- 釋出:使它能夠被當前範圍之外的程式碼所使用
-
執行緒封閉
-
區域性變數
-
ThreadLocal類
ThreadLocal允許你將每個執行緒與持有數值的物件關聯在一起。ThreadLocal提供了get和set訪問器,為每個使用它的執行緒維護一份單獨的拷貝。所以get總是返回由當前執行執行緒通過set設定的最新值。
本質是全域性設定一個容器,通過判斷currentThread,然後存入容器中來實現。
-
單一執行緒對Volatile變數進行寫操作。
-
-
不可變性(不可變物件)
- 不可變物件:只有訪問器方法,可變狀態被封裝
- 不可變物件永遠是執行緒安全的(可變物件的final引用,不叫不可變)
第五章
-
同步容器
-
Vector和Hashtable
-
Collections.synchronizedXxx(Xxx xxx)方法建立的同系列,如:
List t = Collections.synchronizedList(new ArrayList()) ;
-
不要輕易使用/隱藏使用同步容器的toString方法,由於被synchronized關鍵字修飾,序列,所以可能非常耗時。
-
-
併發容器
同步容器通過對容器的所有狀態進行序列訪問,從而實現了它們的執行緒安全。這樣做的代價是削弱了併發性。併發容器就是在此基礎上進行了設計,犧牲了部分的同步性來換取併發效能。
- Queue
- BlockingQueue
- LinkedBlockingQueue
- ArrayBlockingQueue
- PriorityBlockingQueue
- SynchronousQueue
- ConcurrentLinkedQueue
- Deque
- ArrayDeque
- LinkedBlockingDeque
- BlockingQueue
- Map
- ConcurrentHashMap—— Hashtable/ synchronizedMap
- ConcurrentSkipListMap ——synchronizedSortedMap
- Set
- ConcurrentSkipListSet —— synchronizedSortedSet
- List
- CopyOnWriteArrayList —— synchronizedList
- 讀不同步,寫同步
- CopyOnWriteArrayList —— synchronizedList
- Queue
-
阻塞佇列(Blocking Queue) & 生產者-消費者模式
- 阻塞佇列天然支援生產者-消費者模式
- Deque與竊取模式
-
Synchronizer(同步器)
- latch(閉鎖)
- 同步開始與結束
- semaphore(訊號量)
- 實現阻塞佇列
- barrier(關卡)
- cycle
- latch(閉鎖)