JCIP_3_對象的共享_筆記總結

JCIP_3_對象的共享_筆記總結

多個線程訪問共享的可變狀態時需要進行正確的管理,可以通過同步來避免多個線程在同一時刻訪問相同的數據。同步可以確保以原子的方式執行操作和內存可見性。當一個線程修改了對象狀態後其它線程能夠看到發生的變化。

如何安全地共享和發佈對象使其能夠被多個線程同時訪問?

可見性

在沒有同步的情況下我們無法確保執行讀操作的線程能適時地看到其它線程寫入的值,爲了確保多個線程之間對內存寫入操作的可見性必須使用同步機制。

在沒有同步的情況下,編譯器、處理理以及運行時等都可能對操作的執行順序進行一些意想不到的調整。在缺乏足夠同步的多線程程序中,要要對內存操作的執行順序進行判斷,幾乎無法得出正確的結論。

失效數據的典型場景

在沒有或缺乏正確同步的程序中,當某一線程查看某變量時,可能會得到一個已經失效的值,除非在每次訪問變量時都使用同步,否則很可能獲得該變量的一個失效值。失效值可能不會同時出現:一個線程可能獲得某個變量的最新值,而獲得另一個變量的失效值。

非原子的64位數值

64位操作系統下讀寫非volatile類型的64位數值變量是原子操作(區分具體處理器架構的支持),在32位操作系統上讀寫非volatile類型的64位數值變量會被分成兩個32位的操作(一個操作取數值的前32位,另一個操作取數值的後32位)。

在多線程程序中使用共享且可變的longdouble等類型的變量時應使用volatile聲明或使用鎖保護起來。

加鎖與可見性

同步可以用於確保某個線程以一種可預測的方式來查看另一個線程的執行結果,實現同步的方式最簡單的就是同步塊、加鎖。

這塊有一個應用中很典型的點,在訪問某個共享且可變的變量時要求所有線程在同一個鎖上進行同步,在真正寫併發程序時這塊會由於各種原因導致出問題。

加鎖的含義不僅僅侷限於互斥行爲,還包括內存可見性。爲了確保所有線程都能看到共享變量的最新值,所有執行讀或寫操作的線程都必須在同一個鎖上同步。

Volatile變量

volatileJava中提供的一種稍弱的同步機制。

volatile對變量可見性的影響比volatile變量本身更爲重要。

volatile確保對變量的讀操作總會返回最新寫入的值。

讀取volatile變量時不會產生加鎖操作,因此不會使執行線程阻塞。

加鎖機制即可以確保可見性又可以確保原子性,而volatile只能確保變量的可見性。

發佈與逸出

發佈,主要是指對象的引用被傳遞和保存到當前作用域之外。

逸出,是指當某個不應該發佈的對象被髮布了。

當發佈一個對象時,該對象的非私有域中引用的所有對象同樣會被髮布。無論其它的線程會對已發佈的引用執行何種操作,誤用該引用的風險始終存在。當某個對象逸出後,你必須假設有個類或線程可能會誤用該對象。

封裝能夠使得對程序的正確性進行分析變得可能,並使得無意中破壞設計約束條件變得更難。

典型的發佈逸出場景

1.不該發佈的對象被間接的發佈共享。

2.對象還未真正構造完成就被共享。

資料

1.對象構造時this引用逸出比較詳細的解釋,http://stackoverflow.com/questions/3705425/java-reference-escape

線程封閉

訪問共享的可變數據時需要使用同步,避免同步的最簡單方式是僅在單線程內訪問數據,這種技術就是線程封閉。

當某個對象封閉在一個線程中時,這種用法將自動實現線程安全性,即使被封閉的對象本身不是線程安全的。

1.Swing中大量使用了線程封閉,Swing通過將組件或數據模型封裝到Swing的事件分發線程中實現線程安全性。

2.JDBCConnection對象通過使用應用程序的連接池管理,這種連接管理模式在處理請求時隱含地將Connection對象封閉在線程中。

3種實現線程封閉的策略

1.Ad-hoc,通過程序實現來維護線程封閉特性,如手動維護一個類似ThreadLocal機制的全局變量使用volatile保證可見性的特性實現單寫寫多讀;實現類似Swing事件分發線程機制避免多線程共享可變數據。Ad-hoc的實現和維護成本是非常高的並且它的確很脆弱。

2.棧封閉,這是線程封閉的一種特例,棧封閉只能通過局部變量才能訪問對象,局部變量在執行線程的棧中,其它線程無法訪問這個棧。棧封閉需要注意線程的棧空間是有限制的,注意不要使局部變量逸出。

3.ThreadLocal,這個類在通常的應用中使用的很頻繁,ThreadLocal對象通常用於防止對可變的單例變量、全局變量進行共享。常見場景:

3.1.線程池中的Connection

3.2.事務支持,解決事務重入(嵌套)問題。

3.3.線程上下文級別的共享變量

應用系統線如果出現ThreadLocal頻繁的使用說明應用系統的設計有問題,ThreadLocal很容易就被用濫了。

不變性

如果某對象在被創建後其狀態不能被修改,則這個對象是不可變對象。不可變對象不存在中間狀態,不可變對象本身就是線程安全的。

程序設計中一個最困難的地方就是判斷複雜對象的可能狀態,然而,判斷不可變對象的狀態卻很簡單。

對象不變性條件

1.對象創建後其狀態不能被修改

2.對象的所有域都是final類型(由於對象private聲明的域通過類的反射可以訪問和修改所以也需要聲明爲final

3.對象是正確創建的(在對象創建期間沒有產生逸出)

不可變對象與不可變對象引用

不可變對象與不可變對象的引用之間存在着差異,保存在不可變對象中的程序狀態仍然可以更新。通過將一個保存新狀態的實例來替換原有的不可變對象。

Final

Java內存模型中,final域有着特殊的語義,final域能夠確保初始化過程的安全性,從面可以不受限制地訪問不可變對象,並在共享這些對象時無須同步。

對於在訪問和更新多個相關yojgjf出現的競爭條件問題,可以通過將這些變量全部保存在一個不可變對象中來消除。如果是一個不可變對象,那麼當線程獲得了對該對象的引用後,就不必擔心另一個線程會修改對象的狀態。如果要更新這些變量,那麼可以創建一個新的容器對象,但其他使用原有對象的線程仍然會看到對象處於一致的狀態。

p40示例

安全發佈

對象的安全發佈與對象的發佈方式密切相關,有時看似沒有問題的類定義因爲該類的發佈方式不同造成該類沒有被安全的發佈。

由於不可變對象是一種非常重要的對象,因爲Java內存模型爲不可變對象的共享提供了一種特殊的初始化安全性保證,這種保證還將延伸到被正確創建對象中所有final類型的域。

對象安全發佈需要關注的幾點

1.繼承結構,在繼承的類結構下,由於具體初始化邏輯很容易因爲錯誤的繼承特性而導致實例在初始過程中線程安全性已經被破壞。

2.對不可變對象的發佈,由於Java線程模型保證了不可變對象的初始化過程,所以即使在發佈這些對象時沒有使用同布,對象的發佈在多線程環境下也不會產生問題。

3.對可變對象的發佈,可變對象在初始化可以產生代碼重排等特徵,這通常意味着線程在發佈和使用該對象時都必須使用同步。

對象的實例化與對象的安全發佈密切相關,考慮到一個對象未被正確的初始化就被安全發佈的情況。

安全發佈只能確保“發佈當時”對象狀態的可見性,相應的不可變對象因爲發佈後對象狀態不會再被修改所以保證了線程安全。

安全發佈的常用模式

安全發佈的一個對象的約束條件就是對象的引用以及對象的狀態必須同時對其他線程可見。一個正確構造的對象可以通過以下方式來安全地發佈:

1.在靜態初始化函數中初始化一個對象引用

2.將對象的引用保存到volatile類型的域或者AtomicReferance對象中

3.將對象的引用保存到某個正確構造對象的final類型域中

4.將對象的引用保存到一個由鎖保護的域中

5.通過使用線程安全的容器類可以確保安全發佈

事實不可變對象

對象發佈後其狀態不會再被修改,這類對象就是事實不可變對象。

如果對象在發佈後不會被修改,那麼對於其他在沒有額外同步的情況下安全地訪問這些對象的線程來說,是沒有問題的。所有的安全發佈機制都能確保,當對象的引用對所有訪問該對象的線程可見時,對象發佈時的狀態對於所有線程也將是可見的,並且如果對象狀態不會再改變,那麼就足以確保對該對象任何訪問都是安全的。

可變對象

可變對象不僅在發佈對象時需要使用同步,而且在每次對象訪問時同樣需要使用同步來確保對象修改操作的可見性。

對象的發佈需求取決於它的可變性:

1.不可變對象可以通過任意機制發佈。

2.事實不可變對象必須通過安全方式發佈。

3.可變對象必須通過安全方式發佈,並且該對象必須是線程安全或由某個鎖保護起來。

安全地共享對象的一些策略

  1. 線程封閉
  2. 只讀共享
  3. 線程安全共享,線程安全的對象在其內部實現同步,線程通過對象的公有接口進行訪問。
  4. 保護對象,被保護的對象只能通過持有特定的鎖來訪問。

資源

1.http://jcip.net.s3-website-us-east-1.amazonaws.com/listings.html

2.Java併發編程實踐

總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章