JAVA並發實戰學習筆記——3,4章
阿新 • • 發佈:2018-07-20
enc 方法 不變 設定 cti 保護 () col 約束
JAVA並發實戰學習筆記
第三章 對象的共享
- 失效數據:
- java程序實際運行中會出現①程序執行順序對打亂;②數據對其它線程不可見——兩種情況
- 上述兩種情況導致在缺乏同步的程序中出現失效數據這一現象,且“失效”這一情況不確定性很大,因為可能出現可能沒出現。
- JVM中沒有規定對於64位變量如:long, double 的讀寫操作必須是原子的,因此不同步的情況下讀取該類數據可能得到的值無意義(低32位和高32位沒有一起形成完整的數字)
- 商用的JVM一般會讓64位變量的讀取原子化。
- 使用volatile修飾變量可以保證變量的可見性,但不會保證互斥性,是比內置鎖更弱的同步機制。
- 在以下兩種情況下可以使用volatle保證同步。但是過度依賴volatile會使得代碼脆弱、可讀性差。
- 1.只有一個線程對volatile變量實行寫操作;
- 2.變量不需要與其它狀態變量共同參與不變約束。
- 加鎖操作兼保證原子性和可見性
- 在以下兩種情況下可以使用volatle保證同步。但是過度依賴volatile會使得代碼脆弱、可讀性差。
-
發布(將變量的引用保存到其它代碼可以訪問到的地方)與逸出(不該發布的變量被發布了):
- 發布的幾種方式:
- public static A
- 某個對象A的引用在另一個對象B中,發布B則間接地發布了A
- 將對象傳遞給外部方法
- 發布內部類對象A,A中隱含地包含了外部類對象B
- 發布的幾種方式:
-
逸出
- 在構造函數中啟動了新線程(該對象的內部線程類),使得this指針在實例沒有建好時便被其它線程共享了。
- 線程封閉:線程封閉是一類編程的方法,使得各個線程中的對象是不相互共享的,如:JDBC中的Connection對象
- Ad-hoc線程封閉:盡量少用,十分脆弱
- 棧封閉:局部變量由於在運行中是處於線程棧的局部變量表和操作數棧中,所以局部變量無論是否是線程安全,只要其引用不發布出去,都是線程安全的。
- 以能用局部變量就別用全局的變量,全局變量容易引起並發問題。
- ThreadLocal類線程封閉:可將ThreadLocal t 看成是Map<Thread,T> t,保存了每個線程到T對象的一份副本,t.get()得到的是initialValue()設定的值。
- 副本保存在線程中,隨著線程的技術被垃圾回收
- 引入了類之間的耦合性,小心使用
- ThreadLocal類應用場景示例:移植單線程程序到多線程環境
- 不變性
- 滿足下列三個條件的對象滿足“對象不可變
- 創建好後,不提供更改其狀態的方法
- 所有的域都是 final型
- 對象是正確創建的
- “對象引用不可變”————相比於對象不可變,對象的內容可以改變,但是對象地址無法改變(即用final修飾的變量)
- 不可變對象為一系列操作提供弱原子性
- 只要將參數傳入方法,在該方法內就有指向域的引用,其它的線程修改了原對象的域,也不會影響到該方法對原域的訪問。
- 滿足下列三個條件的對象滿足“對象不可變
- 安全發布————確保可以不受JAVA不可見性的影響,得到發布的最新的對象
- 不可變對象安全發布的方式
- final域具有特殊的初始化安全性保證,在初始化的時候即使沒有同步,也可以保證其可見性。
- 可變對象安全發布的方式
- 靜態初始化對象(標有static的對象初始化代碼是在類初始化階段執行的,JVM自帶線程安全特性)
- 將對象的引用保存在volatile類型的域中,或者AtomicReference對象中
- 對象引用存入final類型域中
- 將對象引用存入某個由鎖保護的域中
- 不可變對象安全發布的方式
- 安全訪問
- 事實不可變對象和不可變對象
- 任意訪問
- 可變對象
- 訪問時需要同步機制,對象需要是線程安全,或者訪問前先獲得某個鎖
- 事實不可變對象和不可變對象
第四章 對象的組合
- 如何建立線程安全的類
- 1.收集同步需求————候選範圍為對象的域,包括對象中包含的基本類型變量,以及域對象,和域對象內的域
- 不變性條件
- 單個變量,即變量的值需要在其合法範圍內
- 多個變量,即多個變量的值之間需要滿足某些約束
- 訪問同一個不變性條件中任何一個變量,都需要獲得同一個鎖,以確保對操作不會破壞不變性條件
- 後驗條件
- 變量的值轉換需要滿足的約束
- 不變性條件和後驗條件約束了對象的哪些狀態和狀態轉換是有效的
- 不變性條件
- 2.先驗條件————即依賴狀態的操作
- 1.收集同步需求————候選範圍為對象的域,包括對象中包含的基本類型變量,以及域對象,和域對象內的域
-
實例封閉————使封裝的數據被封閉在另一個對象中,被封閉的對象不超出其作用域????
-
一般知識
- 將對實例內封裝的對象的訪問限制在對象的方法上,以確保線程在訪問數據是總能持有正確的鎖.
- 含有線程不安全的內部對象的線程安全類示例:
- Collections.synchronized***()方法通過裝飾器模式將容器封裝在一個線程安全的對象中
public class PersonSet{ private final Set myset = new HashSet; public synchronized void addPerson(Person p){ myset.add(p); } public synchronized boolean containsPerson(Person p){ return myset.contains(p); } }
- 疑惑之處:
- 不超出作用域?如何保證方法有合適的返回值呢?覺得很奇怪。
- 安全發布狀態變量的三個條件
- 1. 狀態變量是線程安全的
- 2. 變量不存在不變性約束
- 3. 在方法中不包含使得該變量進入不合法狀態的操作
- 安全發布狀態變量的三個條件
- 而且使用鎖同步,和不使對象溢出有什麽聯系呢?我覺得這就是兩個獨立的東西,放在一起湊成一節,讓人困惑
- 猜測:要實現非線程封閉的線程安全類,封閉對象是第一步,限制訪問方法並使訪問方法同步是第二步。
- 不超出作用域?如何保證方法有合適的返回值呢?覺得很奇怪。
- Java監視器模式(即私有的所對象,而非內置鎖)
- 好處是不會讓外部的方法得到該對象的鎖
- 若容器內的對象是非線程安全的,可以每次發布該對象的時候都深度復制
-
- 線程安全性的委托
- 將線程安全性委托給單個/多個(彼此獨立的對象,且所有的方法中都不包含無效狀態轉換操作)線程安全的狀態
- 若類中包含不符合上一條要求的,包含多個有不
- 條件約束的狀態,實現線程安全性需要加鎖機制
- 在現有的線程安全類中添加新功能
- 在並發中,能用現成的線程安全類就盡量用;若現成的類滿足不了需求,則可能選擇添加新功能
- 四種方法:
- 改源碼 ————不現實
- 擴展基礎類 ———— 在自己的子類新添加的方法中使用內置鎖——從這裏來看,子類和父類中使用的內置鎖應該是同一個,都是真對實例而言的。
- 缺點:破壞了類的封裝性
- 客戶端加鎖機制 ———— 了解基礎類對象使用的是什麽鎖,在客戶端代碼中使用相同的鎖
- 缺點:破壞了同步策略的封裝性
- 組合模式 ————使用裝飾器模式將基礎類如:list等封裝在內部,將list的方法包裝一層,使用裝飾器類的內置鎖。
- 優點
- 不會破壞封裝性
- 健壯性更強
- 即使容器類不是線程安全的,也可以借此實現線程安全
- 缺點
- 多加一層鎖,效率下降
- 優點
- 文檔!!!文檔!!!————要想使開發和用戶使用遵從本類的安全機制,必須有文檔記錄
- 設計文檔 & 用戶文檔
- Java文檔中應該註明該類使用的同步機制,應該包含下面一些內容
- 是否是線程安全的?
- 客戶回調需不需要加鎖,可以加那些鎖?
- 哪些鎖保護了哪些狀態?(設計文檔,可以用java註釋便於後續開發)
JAVA並發實戰學習筆記——3,4章