「Java併發編程實戰」之對象的共享

前言

  本系列博客是對《Java併發編程實戰》的一點總結,本篇主要講解如下幾個內容,內容會比較枯燥。可能你們看標題不能能直觀的感覺出到底什麼意思,這就是專業術語,哈哈,解釋下,術語(terminology)是在特定學科領域用來表示概念的稱謂的集合,在我國又稱爲名詞或科技名詞(不一樣於語法學中的名詞)。術語是經過語音或文字來表達或限定科學概念的約定性語言符號,是思想和認識交流的工具。我就用白話文來給你們解釋下這些術語。java

線程安全

  什麼是線程安全?這算是老生常談的問題了,相信你們在面試的過程當中也遇到過,在線程安全的定義中,最核心的概念就是正確性,若是對線程安全性的定義是模糊的,那麼就是缺少對正確性的清晰定義。正確性的含義是,某個類的行爲與其規範徹底一致,在良好的規範中一般會定義各類不變性條件來約束對象的狀態,以及定義各類後驗條件來描述對象操做的結果。說白了就是一個類不管是在單線程環境仍是多線程環境中都能正確的執行,那麼這個類就是線程安全的。若是在線程交替執行的過程當中致使不可預料的結果,那麼就是線程不安全的。面試

可見性

  假若有一個變量,如今對它進行讀寫操做,可見性說的就是當前線程對變量的寫操做是否對其它線程可見,就是其它線程能不能知道你對這個變量作了修改。若是不能保證可見,必須使用同步機制。不然當其餘線程來讀這個變量的時候,可能會獲得一個已經失效的值。這個值就被稱爲失效數據。
  在這裏提醒你們,對於非volatile類型的long和double變量JVM容許將64位的讀操做或寫操做分解爲兩個32位的操做,當讀一個非volatile類型的long變量時,若是讀寫操做是在不一樣的線程中執行,那麼極可能會讀取到某個值的高32位和另外一個值的低32位,因此在多線程環境中使用共享可變的long和double等類型的變量時不安全的,除非使用關鍵字volatile來聲明它們,或者用鎖保護起來。數據庫

  1. 如今來介紹一下Volatile: Java語言提供了一種稍弱的同步機制,即volatile類型,用來確保將變量的更新操做通知到其餘線程。使用就是在變量前面加上volatile便可。在 JMM 中,線程之間的通訊採用共享內存來實現的。volatile 的內存語義是:
  • 當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量值,當即刷新到主內存中。
  • 當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存設置爲無效,直接從主內存中讀取共享變量。
  1. volatile的使用條件:
  • 對變量的寫入操做不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。
  • 該變量不會與其餘狀態變量一塊兒歸入不變性條件中。
  • 在訪問變量時不須要加鎖。

加鎖機制既能夠確保可見性又能夠確保原子性,而volatile變量只能確保可見性,千萬不要用它來確保原子性操做。編程

發佈與逸出

發佈一個對象的意思就是使對象可以在當前做用域以外的代碼中使用,例如,將一個指向該對象的的引用保存到其餘代碼能夠訪問的地方,或者在某一個非私有的方法中返回該引用,或者將引用傳遞到其餘類方法中。當某個不該該發佈的對象被髮布時,這種狀況被稱爲逸出。安全

線程封閉

當訪問共享的可變數據時,一般須要使用同步。一種避免使用同步的方式就是不一樣享數據,若是僅在單線程內訪問數據,就不須要同步,這種技術被稱爲線程封閉,它是實現線程安全性最簡單的方法之一。下面介紹幾種線程封閉技術。bash

  1. Ad-hoc線程封閉多線程

    Ad-hoc線程封閉是指,維護線程封閉性的職責徹底有程序實現來承擔。例如可見性修飾符或局部變量,能將對象封閉到目標線程上。事實上對於線程封閉對象一般保存在共有變量中。Ad-hoc線程封閉是很是脆弱的,因此程序中儘可能少使用它,可使用如下兩種技術(棧封閉,ThreadLocal)。併發

  2. 棧封閉ide

    棧封閉也被成爲線程內部使用或者線程局部使用,不要與ThredaLocal混淆,比Ad-hoc更易於維護,也更加健壯。在棧封閉中,只能經過局部變量才能訪問對象。函數

//僞代碼
public void test(){
//定義一個變量
Set set ;
// 實例化一個TreeSet對象,並將該對象的一個引用保存到set中。
set = new TreeSet();
}
複製代碼

這樣TreeSet對象就被封閉在局部變量中,所以也被封閉到執行線程中,它位於執行線程的棧中,其餘線程沒法訪問這個棧。

  1. ThreadLocal

    維持線程封閉性的一種更爲規範的方法是使用ThreadLocal,這個類能使線程中的某個值與保存值的對像關聯起來,ThreadLocal提供了get與set等訪問接口或方法,這些方法爲每一個使用該變量的線程都存有一份獨立的副本,所以get老是返回由當前線程執行set時設置的最新值。ThreadLocal一般用於防止對可變對像的單實例變量或全局變量進行共享。

//保存一個數據庫鏈接對像
   public static ThreadLocal<Connection> connectionThreadLocal = 
   new ThreadLocal<Connection>(){
       @Override
       protected Connection initialValue() {
           return DriverManager.getConnection(DB_URl);
       }
   };
   //每一個線程使用時直接get
   public static Connection getConnection(){
       return connectionThreadLocal.get();
   }
複製代碼

不變性

若是某個對像在被建立以後其狀態就不能被修改,那麼這個對象就是不可變對象,線程安全性是是不可變對象的固有屬性之一。當知足一下條件時,對象纔是不可變的:

  • 對象建立後其狀態就不能修改。
  • 對象的全部域都是final類型。
  • 對象是正確建立的(在對象的建立期間,this引用沒有逸出)

Final域: 用於構造不可變對象。final類型的域是不能修改的(但若是final域所引用的對象是可變的,那麼這些引用的對象是能夠修改的)。然而在java內存模型中,final域還有着特殊的語義。final域能確保初始化過程的安全性,從而能夠不受限制的訪問不可變對象,並在共享這些對象時無須同步。

安全發佈

  1. 要安全發佈一個對象,對象的引用以及對象的狀態必須同時對其餘線程可見,一個正確構造的對象能夠經過如下方式來安全的發佈:
  • 在靜態初始化函數中初始化一個對象引用。
  • 將對象的引用保存到volatile類型的域或者Atomicreferance對象中。
  • 將對象的引用保存到某個正確構造對象的final類型域中。
  • 將對象的引用保存到一個由鎖保護的域中。
  1. 在併發程序中使用和共享對象時,可使用一些實用的策略:
  • 線程封閉:線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,而且只能由這個線程修改
  • 只讀共享:在沒有額外同步的狀況下,共享的只讀對象能夠由多個線程併發訪問,但任何線程都不能修改它,共享的只讀對象包括不可變對象和事實不可變對象。
  • 線程安全共享:線程安全的對象在其內部實現同步,所以多線程能夠經過對象的公有接口來進行訪問而不須要進一步的同步。
  • 保護對象:被保護的對象只能經過持有特定的鎖來訪問,保護對象包括封裝在其餘線程安全對象中的對象,以及已發佈的而且由某個特定鎖保護的對象。

你們看后辛苦點個贊點個關注哦!查看我的主頁,有更多的博客哦。若有錯誤,煩請指正。 有興趣加羣一塊兒交流。

相關文章
相關標籤/搜索