前段時間看完了《並發編程的藝術》,總感覺自己對於並發缺少一些整體的認識。今天借助《Java並發編程實踐》,從一些基本概念開始,重新整理一下自己學過並發編程。從並發基礎開始,深入進去,系統學習一下並發編程。
編寫線程安全的代碼,核心在於要對狀態訪問操作進行管理,特別是對共享的(Shared)和可變的(Mutable)狀態的訪問。對象的狀態是指存儲在狀態變量(實例或靜態域)中的數據。對象的狀態還可能包括其他依賴對象的域。(Map.Entry)
一個對象是否需要時線程安全的,取決於該對象是否被多線程訪問。這指的是程序中訪問對象的方式,而不是對象要實現的功能。要使得對象是線程安全的,要采用同步機制來協同對對象可變狀態的訪問。Java常用的同步機制是Synchronized,還包括 volatile類型的變量,顯示鎖以及原子變量。
線程安全的程序是否完全由線程安全的類構成?答案是否定的,完全由線程安全的類構成的程序並不一定是線程安全的,線程安全類中也可以包含非線程安全的類。只有當類中僅包含自己的狀態時,線程安全類才有意義!
什么是線程安全性?
當多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行為,那么這個類就是線程安全的。
正確性:某個類的行為與其規范相一致。(我理解的規范就是我們在編寫類時,能預知的狀態結果)
原子性
競態條件(Race Condition):某個計算的正確性取決於多個線程的交替執行的時序。(線程的時序不同,產生的結果可能會不同)
“先檢查后執行”,即通過一個可能失效的觀測結果來決定下一步的操作。
首先觀察到某個條件為真,然后開始執行相關的程序,但是在多線程的運行環境中,條件判斷的結果以及開始執行程序中間,觀察結果可能變得無效(另外一個線程在此期間執行了相關的動作),從而導致無效。常見的就是(Lazy Singleton)

1 /** 2 * describe: 懶漢式單例模式 3 * 優點:只有在需要使用LazySingleton1對象時,才真正生成一個LazySingleton1對象 4 * 缺點:會因為某些Java 平台內存模型允許無序寫入,使得getInstance方法可能返回 5 * 一個尚未執行構造函數的對象 6 * Created by tianc on 2017/4/15. 7 */ 8 public class LazySingleton { 9 private static LazySingleton lazyInstance = null; 10 private LazySingleton() { 11 } 12 public static LazySingleton getInstance(){ 13 if(lazyInstance == null){ 14 synchronized (LazySingleton.class){ 15 if(lazyInstance == null){ 16 lazyInstance = new LazySingleton(); 17 } 18 } 19 } 20 return lazyInstance; 21 } 22 }
“讀取-修改-寫入”,基於對象之前的狀態來定義對象狀態的轉換。即使是volatile修飾的變量,在多線程的環境里面進行自增操作,同樣會發生競態條件,所以volatile不能保證絕對的線程安全(360面試問題)。
引用書中定義:假定有兩個操作A和B,如果從執行A的線程來看,當另一個線程執行B時,要么將B完全執行完,要么完全不執行B,那么A和B對彼此來說是原子的。原子操作是指:對於訪問同一個狀態的所有操作(包括該操作本身)來說,這個操作是一個以原子方式執行的操作。
加鎖機制
在線程安全的定義中,多個線程間的操作無論采用何種執行時序或交替方式,都要保證不變性條件不被破壞。當不變性條件中涉及多個變量時,各個變量之間並不是互相獨立的,一個變量發生變化會對其他變量的值產生約束。因此,一個變量發生改變,在同一個原子操作里面,其他相關變量也要更新。
內置鎖:同步代碼塊(Synchronized Block)包括兩部分:一個作為鎖的對象引用,一個作為由這個鎖保護的代碼塊。關鍵字Synchronized修飾方法就是一種同步代碼塊,鎖就是方法調用所在的對象,靜態的Synchronized方法以Class對象作為鎖。內置鎖或監視鎖就是以對象作為實現同步的鎖。
Java內置鎖,進入的唯一途徑是執行進入由鎖保護的同步代碼塊或方法。它相當於一種互斥鎖。
重入鎖:當一個持有鎖的線程再次請求進入自己持有的鎖時,該請求會成功。"重入"意味着獲取鎖的操作的粒度是“線程”,而不是“調用”。重入的一種實現方式,為每個鎖關聯一個計數器和線程持有者。
用鎖來保護狀態
由於鎖能使其保護的代碼路徑以串行形式訪問,因此可以通過鎖來構造一些協議以實現對共享狀態的獨占訪問。
對象的內置鎖與其狀態之間沒有內在的聯系,雖然大多數類都將內置鎖用做一種有效的加鎖機制,但對象的域並不一定要通過內置鎖來保護。
對於每個包含多個變量的不變性條件,其中涉及的所有變量都要使用同一個鎖來保護/同步。