在多線程程序中,咱們不只但願防止某個線程正在使用對象狀態而另外一個線程在同時修改該狀態,並且但願確保當一個線程修改了對象狀態後,其餘線程可以看到發生的狀態變化。若是沒有同步,那麼這種狀況就沒法實現。緩存
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { @Override public void run() { while (!ready) Thread.yield(); System.out.println(number); } } public static void main(String[] args) { new ReaderThread().start(); number = 42; ready = true; } }
在上面代碼中,結果可能會輸出0。由於在缺乏同步的狀況下,Java內存模型容許編譯器對操做順序進行重排序,並將數值緩存在寄存器中,它還容許CPU對操做順序進行重排序,並將數值緩存在特定的緩存中。安全
非原子類的64位操做 Java內存模型要求,變量的讀取操做和寫入操做都必須是原子操做,但對於非volatile類型的long和double變量,JVM容許將64位的讀操做或寫操做分解爲兩個32位的操做,從而破壞了原子性,除非用關鍵字「volatile」來聲明它們或者使用鎖來保護它們。多線程
內置鎖能夠用於確保某個線程以一種可預測的方式來查看另外一個線程的執行結果。 加鎖的含義不只僅侷限於互斥行爲,還包括內存可見性,爲了確保全部線程都能看到共享變量的最新值,全部執行讀操做或寫操做的線程都必須在同一個鎖上同步。併發
volatile變量 Java提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操做通知到其餘線程。當把變量聲明爲volatile類型後,編譯器與jre都會注意到這個變量是共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序。 但volatile並不會加鎖,所以也就不會產生阻塞行爲。因此,volatile變量是一種比synchronized更輕量級的同步機制。 volatile變量一般用做某個操做完成、發生中斷或者正太改變的標誌,在使用時應很是當心,例如,volatile的語義不足以確保遞增(++)操做的原子性。ide
使用volatile變量的時機:函數
發佈(Publish)一個對象:使對象可以在當前做用域以外的代碼中使用。this
class UnsafeStates { private String[] states = new String[] {"AK","AL"...}; public String[] getStates() {return states;} }
逸出(Escape):一個不應被髮布的對象被髮布線程
將數據或對象封閉在一個線程中的技術叫作「線程封閉」。線程封閉將自動實現線程安全性,即便被封閉的對象不是線程安全的。 Java提供了一些機制來幫助實現線程封閉性,如局部變量和ThreadLocal類,但使用時要確保封閉在線程中的對象不會從線程中逸出。 在volatile變量上存在一種特殊的線程封閉,只要確保只有單個線程對共享的volatile變量執行寫入操做,那麼就能夠安全地在這些共享的volatile變量上執行「讀取--修改--寫入」的操做,在這種狀況下,至關於將修改操做封閉在單個線程中以防止發生競態條件,而且volatile變量的可見性保證還確保了其餘線程能看到最新的值。code
知足同步需求的另外一種方法是使用不可變對象。 ** 不可變對象老是線程安全的。**對象
不可變對象不等於將對象中全部的域都聲明爲final類型,即便對象中全部的域都是final類型的,這個對象也仍然是可變的,由於在final類型的域中能夠保存對可變對象的引用。 不可變對象知足的條件: 1. 對象建立之後其狀態不能修改 2. 對象的全部域都是final類型(Java中,final除了表示不可變,還表示對象初始化過程是安全的) 3. 對象是正確建立的(建立是this沒有逸出)
要安全地發佈一個對象,對象的引用以及對象的狀態必須同時對其餘線程可見。 安全發佈的經常使用模式: 1. 在靜態初始化函數中初始化一個對象引用 2. 將對象的引用保存到volatile類型的域或者AtomicReference對象中 3. 將對象的引用保存到某個正確構造對象的final類型域中 4. 將對象的引用保存到一個由鎖保護的域中
public static Holder holder = new Holder(42);
靜態初始化器由JVM在類的初始化階段執行,因爲JVM內部存在着同步機制,所以經過這種方式初始化的任何對象均可以被安全地發佈。
事實不可變對象:若是對象從技術上來看是可變的,但其狀態在發佈後不會再改變,那麼把這種對象稱爲「事實不可變對象(Effectively Immutable Object)」 在沒有額外同步的狀況下,任何線程均可以安全地使用被安全發佈的事實不可變對象。
對象的發佈需求取決於它的可變性: 1. 不可變對象能夠經過任意機制來發布 2. 事實不可變對象必須經過安全方式來發布 3. 可變對象必須經過安全方式來發布,而且必須是線程安全的或者由某個鎖保護起來
在併發程序中使用和共享對象時,可使用一些實用策略: 1. 線程封閉。線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,而且只能由這個線程修改 2. 只讀共享。在沒有額外同步的狀況下,共享的只讀對象能夠由多個線程併發訪問,任何線程都不能修改它。共享的只讀對象包括不可變對象和事實不可變對象 3. 線程安全共享。線程安全的對象在其內部實現同步,所以多個線程能夠經過對象的公有接口來進行訪問而不須要進一步的同步 4. 保護對象。被保護的對象只能經過持有特定的鎖來訪問。保護對象包括封裝在其餘線程安全對象中的對象,以及已發佈的而且由某個特定鎖保護的對象。