Java內存模型(memory model)分爲主存儲器(main memory)和工做存儲器(working memory)兩種。設計模式
主存儲器(main memory):
類的實例所存在的區域,main memory爲全部的線程所共享。安全
工做存儲器(working memory):
每一個線程各自獨立所擁有的做業區,在working memory中,存有main memory中的部分拷貝,稱之爲工做拷貝(working copy)。性能
線程沒法直接對主存儲器進行操做,當線程須要引用實例的字段的值時,會一次將字段值從主存儲器拷貝到工做存儲器上(至關於上圖中的read->load)。
當線程再次須要引用相同的字段時,可能直接使用剛纔的工做拷貝(use),也可能從新從主存儲器獲取(read->load->use)。
具體會出現哪一種狀況,由JVM決定。atom
因爲線程沒法直接對主存儲器進行操做,因此也就沒法直接將值指定給字段。
當線程欲將值指定給字段時,會一次將值指定給位於工做存儲器上的工做拷貝(assign),指定完成後,工做拷貝的內容便會複製到主存儲器(store->write),至於什麼時候進行復制,由JVM決定。
所以,當線程反覆對一個實例的字段進行賦值時,可能只會對工做拷貝進行指定(assign),此時只有指定的最後結果會在某個時刻拷貝到主存儲器(store-write);也可能在每次指定時,都進行拷貝到主存儲器的操做(assign->store->write)。spa
Java語言規範定義了線程的六種原子操做:線程
線程欲進入synchronized時,會執行如下兩類操做:設計
當線程欲進入synchronized時,若是該線程的工做存儲器(working memory)上有未映像到主存儲器的拷貝,則這些內容會強制寫入主存儲器(store->write),則這些計算結果就會對其它線程可見(visible)。code
當線程欲進入synchronized時,工做存儲器上的工做拷貝會被所有丟棄。以後,欲引用主存儲器上的值的線程,一定會從主存儲器將值拷貝到工做拷貝(read->load)。內存
線程欲退出synchronized時,會執行如下操做:rem
當線程欲退出synchronized時,若是該線程的工做存儲器(working memory)上有未映像到主存儲器的拷貝,則這些內容會強制寫入主存儲器(store->write),則這些計算結果就會對其它線程可見(visible)。
注意: 線程欲退出synchronized時,不會執行工做存儲器(working memory)的釋放 操做。
volatile具備如下兩種功能:
volatile只能作內存同步,不能取代synchronized關鍵字作線程同步。
當線程欲引用volatile字段的值時,一般都會發生從主存儲器到工做存儲器的拷貝操做;相反的,將值指定給寫着volatile的字段後,工做存儲器的內容一般會當即映像到主存儲器
設計模式中有一種單例模式(Singleton Pattern),一般採用鎖來保證線程的安全性。
Main類:
//兩個Main線程同時調用單例方法getInstance public class Main extends Thread { public static void main(String[] args) { new Main().start(); new Main().start(); } public void run() { System.out.println(Thread.currentThread().getName() + ":" + MySystem.getInstance().getDate()); } }
單例類:
//採用延遲加載+雙重鎖的形式保證線程安全以及性能 public class MySystem { private static MySystem instance = null; private Date date = new Date(); private MySystem() { } public Date getDate() { return date; } public static MySystem getInstance() { if (instance == null) { synchronized (MySystem.class) { if (instance == null) { instance = new MySystem(); } } } return instance; } }
分析:
上述Main類的MySystem.getInstance().getDate()
調用可能返回null或其它值。
假設有兩個線程A和B,按照如下順序執行:
當線程A執行完A-4且未退出synchronized時,線程B開始執行,此時B得到了A建立好的instance實例。
可是注意,此時instance實例可能並未徹底初始化完成。
這是由於線程A製做MySystem實例時,會給date字段指定值new Date(),此時可能只完成了assign操做(線程A對工做存取器上的工做拷貝進行指定),在線程A退出synchronized時,線程A的工做存儲器上的值不保證必定會映像到主存儲器上(store->write)。
因此,當線程B在線程A退出前就調用MySystem.getInstance().getDate()方法的話,因爲主存儲器上的date字段並未被賦值過,因此B獲得的date字段就是未初始化過的。
注意:上面描述的這種狀況是否真的會發生,取決於JVM,由Java語言規範決定。
解決方法:
採用懶加載模式,在MySystem類中直接爲instance 字段賦值:private static MySystem instance = new MySystem();