對象的共享

1.可見性:咱們不只但願防止某個線程正在使用對象狀態而另外一個線程在同時修改該狀態,並且但願確保當一個線程修改了對象狀態後,其餘線程可以看到發生的狀態 變化。java

 

 

NoVisibility可能會持續循環下去,由於讀線程可能永遠都看不到ready的值。還有一種狀況下,可能會輸出0,他看到了ready,卻沒有看到number,這種狀況叫作「重排序」數組

也就是說,在沒有同步的狀況下,編譯器、處理器以及運行時均可能對操做的執行順序進行一些意想不到的調整。緩存

 

1.1    失效數據安全

NoVisibility展示了一種可能產生錯誤結果的一種狀況:失效數據。多線程

 

 

這個類不是現成安全的,get和set都是在沒有同步的狀況下訪問value的。若是某個線程調用了set,那麼另外一個正在調用get的線程可能會看到set之後的數據,也可能看不到併發

 

 

經過使用同步方法,就成了一個線程安全的類函數

1.2    非原子的64位操做this

線程在沒有同步的狀況下,讀取的一個失效值,再怎麼也是以前某個線程設置的,而不是隨機的,這叫作最低安全性,不過有一個例外,非volatile類型的64位數值變量(double和long).JVM容許將64的讀操做或寫操做分解爲兩個32位的操做。若是讀取一個非volatile類型的long變量,讀和寫在不一樣的線程內執行,極可能獲得一個值得高32位和另外一個值得低32位。所以,在多線程程序中使用共享且可變的long和double等類型的變量也是不安全的!除非用volatile來聲明他們,或者用鎖保護起來spa

1.3    加鎖與可見性線程

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

1.4    Volatile變量

一種稍弱的同步機制,用來確保將變量的更新通知到其餘線程。

當把一個變量聲明爲volatile類型以後,編譯器和運行時都會注意到這個變量時共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序,所以在讀取volatile類型的變量時總會返回最新寫入的值。

僅當volatile變量能簡化代碼的實現以及對同步策略的驗證時,才應該使用它們。Volatile變量的正確使用方式是:確保它們自身狀態的可見性,確保他們所引用對象的狀態的可見性,以及表示一些重要的程序生命週期事件的發生。

 

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

當且僅當知足如下全部條件時,纔可使用volatile變量:

  1. 對變量的寫入操做不依賴變量的當前值,或者你能確保只有單個線程更新變量的值。
  2. 該變量不會與其餘狀態變量一塊兒歸入不變性條件中
  3. 在訪問變量時不須要加鎖

2.發佈和逸出

「發佈」一個對象指的是使對象可以在當前做用域以外的代碼中使用。當某個不應發佈的對象被髮布時,這種狀況就叫作「逸出」

將一個指向該對象的引用保存到其餘代碼能夠訪問的地方

 

發佈對象的最簡單方法就是將對象的引用保存到一個公有的靜態變量中。在initialize方法中實例一個新的HashSet對象,併發布。當發佈某個變量時,可能會間接的發佈其餘對象若是將一個Sectet對象添加到knownSecrets中,那麼一樣會發布這個對象。

 

在一個非私有的方法中返回該引用

 

 

若是按照上述方式來發布states,就會使內部的可變狀態逸出。任何調用者均可以改變這個數組的內容

 

將引用傳遞到其餘類的方法中

 

 

發佈一個內部的類實例,當發佈EventListener()時,也隱含的發佈了ThisEscape實例自己。

 

安全的對象構造過程

上面從構造函數中發佈對象時,只是發佈了一個還沒有構造完成的對象。即便發佈對象位於構造函數的最後一行也是如此。所以不要在構造過程當中使this引用逸出。

 

 

若是想在構造函數中註冊一個事件監聽器或者啓動線程,那麼可使用一個私有的構造函數和一個公共的工廠方法。

3 線程封閉

爲了不使用同步的方式就是不共享數據,若是僅在單線程內訪問數據,就不須要同步,這種技術叫作線程封閉

當某個被封閉的對象自己不是線程安全的封閉在一個線程中時,也將自動實現線程安全性

3.1  Ad—hoc線程封閉

指的是:維護線程封閉性的職責徹底由程序來實現承擔!

在volatile中存在一種特殊的線程封閉。只要能確保只有單個線程對共享的volatile變量執行寫入操做,那麼就能夠安全的在這些共享的volatile變量上執行「讀取-修改-寫入」操做。在這種狀況下,至關於將修改操做封閉在當個線程中以防止發生競態條件,而volatile的可見性還保證了其餘線程能夠看到最新的值

3.2棧封閉

一種特例。在棧封閉中,只能經過局部變量才能訪問對象。局部變量的固有屬性之一就是封閉在執行線程中。它們位於執行線程的棧中,其餘線程沒法訪問這個棧。棧封閉也被稱爲線程內部使用或者線程局部使用

3.3 ThreadLocal類

 這個類能使線程中的某個值與保存值得對象關聯起來。ThreadLocal提供了get與set等訪問接口或方法

這個類爲提供了get和set方法,這些方法爲每一個使用該變量的線程都存有一份獨立的副本,所以get老是返回由當前執行線程再調用set時設置的最新值。

ThreadLocal對象一般用於防止對可變的單實例變量或全局變量進行共享

 

JDBC的鏈接對象不必定是安全的,所以,當多線程應用程序在沒有協同的狀況下使用全局變量時,就不是線程安全的。經過把JDBC的鏈接保存到ThreadLocal中,每一個線程都會擁有屬於本身的鏈接

4 不變性

不可變對象必定是線程安全的!

當知足一下條件,對象纔是不可變得:

1.對象建立之後他的狀態就不能改變

2.對象的全部域都是final類型

3.對象時正確建立的(對象建立期間,this引用沒有逸出)

在不可變對象的內部可使用可變對象來管理它們的狀態

4.1 Final域(域是一種屬性,能夠是一個類變量,一個對象變量,一個對象方法變量或者是一個函數的參數)

final類型的域是不能修改的(若是final域引用的對象時可變的,那麼這些被引用的對象時能夠修改的

4.2 示例:使用Volatile類型來發布不可變對象

建立一個對數值和因數分解結果進行緩存的不可變容器類

因式分解Servlet裏面有兩個原子操做:更新緩存的結果,以及經過判斷緩存中的數值是否等於請求的數值來決定是否直接讀取緩存中的因數分解結果。

若是是一個不可變得對象,一個線程得到了該對象的引用時,沒必要擔憂另外一個線程修改,若是想要更新這些變量,那麼久能夠建立一個新的容器對象,其餘使用原有對象的線程由於volatile仍然會看到最新的結果

public class VolatileCachedFactorizer implements Servlet{
//volatile對象確保可見性! private volatile OneValueCache cache=new OneValueCache (null,null);
public void service(ServletRequest req,ServletResponse resq){ BigInteger i=extractFromRequest(req);
//判斷是否有緩存,OneValueCache是不可變的,因此不怕其餘線程干擾 BigInteger[] factors=cache.getFactors(i); if(factors==null){ factros=factor(i);
//更新cache變量,建立新的容器對象 cache=new OneValueCache(i,factros); } encodeIntoResponse(resp,factors); } }

  

 5 安全發佈

 

public Holder holder;
public void initialize(){
  holder=new Holder(42);
}

 

  

 

5.1 不正確的發佈:正確的對象被破壞

若是使用上面的方式發佈Holder,因爲沒有使用同步來確保Holder對象對其餘線程可見,所以Holder被稱爲「未被正確發佈」。

未被正確發佈的對象有兩個問題:除了發佈對象的線程外,其餘線程可能看到的Holder域是一個失效值。也可能看到Holder引用的值是最新的,可是Holder狀態的值倒是失效的。

5.2 不可變對象與初始化安全性

任何線程均可以在不須要額外同步的狀況下安全的訪問不可變對象,即便在發佈這些對象時沒有使用同步。

若是上面的Holder對象時不可變得,那麼即便Holder沒有被正確的發佈,也不會拋出異常。

5.3 安全發佈的經常使用模式

安全發佈的方式:

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

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

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

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

要發佈一個靜態夠早的對象,最簡單和最安全的方式是使用靜態的初始化塊

public static Holder holder=new Holder(42);

靜態初始化塊由JVM在類的初始化階段執行,因爲在JVM內部存在着同步機制,所以經過這種方式初始化的任何對象均可以被安全的發佈!

5.4 事實不可變對象

若是對象從技術上來看是可變的,但其狀態在發佈後不會再改變,那麼就把這種對象叫作「事實不可變對象」

例如:Date自己是可變的,若是視做不可變對象,那麼在多個線程之間共享Date對象時,就能夠省去對鎖的使用。

public Map<String,Date> lastLogin=Collections.synchronizedMap(new HashMap<String,Date>());

若是Date對象的值在被放入Map後就不會改變,那麼synchronizedMap中的同步機制就足以使Date值被安全的發佈,而且在訪問這些Date值時不須要額外的同步。

5.5 可變對象

 對於可變對象,不只在發佈時須要使用同步,在每次對象訪問的時候也得使用同步。

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

1.不可變對象能夠經過任意機制來發布

2.事實不可變對象必須經過安全方式來發布

3.可變對象必須經過安全方式來發布,而且必須是線程安全的或者由某個鎖保護起來

5.6 安全地共享對象

 在併發程序中使用和共享對象時,可使用一些實用的策略

1.線程封閉 線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,而且只能由這個線程修改

2.只讀共享  在沒有額外同步的狀況下,共享的只讀對象能夠由多個線程併發訪問,但任何線程都不能修改它。共享的只讀對象包括不可變對象和事實不可變對象

3.線程安全共享  線程安全的對象在其內部實現同步,所以多個線程能夠經過對象的公有接口來進行訪問而不須要進一步的同步

4.保護對象    被保護的對象只能經過持有特定的鎖來訪問。保護對象包括封裝在其餘線程安全對象中的對象,以及已發佈的而且由某個特定鎖保護的對象

相關文章
相關標籤/搜索