該系列文章已收錄在公衆號【Ccww技術博客】,原創技術文章早於博客推出
在深刻理解使用Volatile與Synchronized時,應該先理解明白Java內存模型 (Java Memory Model,JMM)java
Java內存(JMM)模型是在硬件內存模型基礎上更高層的抽象,它屏蔽了各類硬件和操做系統對內存訪問的差別性,從而實現讓Java程序在各類平臺下都能達到一致的併發效果。安全
JMM的內部工做機制多線程
JMM內部會有指令重排,而且會有af-if-serial跟happen-before的理念來保證指令的正確性併發
Java內存模型爲了解決多線程環境下共享變量的一致性問題,包含三大特性,app
在理解了JMM的時,來說講Volatile與Synchronized的使用,Volatile與Synchronized到底有什麼做用呢?jvm
Volatile 的特性:性能
當寫一個volatile變量時,JMM會把該線程對應的工做內存中的共享變量值更新後刷新到主內存,優化
當讀取一個volatile變量時,JMM會把該線程對應的工做內存置爲無效,線程會從主內存中讀取共享變量。this
寫操做:
spa
讀操做:
JMM對volatile的禁止指令重排採用內存屏障插入策略:
在每一個volatile寫操做的前面插入一個StoreStore屏障。在每一個volatile寫操做的後面插入一個StoreLoad屏障
在每一個volatile讀操做的後面插入一個LoadLoad屏障。在每一個volatile讀操做的後面插入一個LoadStore屏障
Synchronized是Java中解決併發問題的一種最經常使用的方法,也是最簡單的一種方法。Synchronized的做用主要有三個:
Synchronized總共有三種用法:
- 當synchronized做用在實例方法時,監視器鎖(monitor)即是對象實例(this);
- 當synchronized做用在靜態方法時,監視器鎖(monitor)即是對象的Class實例,由於Class數據存在於永久代,所以靜態方法鎖至關於該類的一個全局鎖;
- 當synchronized做用在某一個對象實例時,監視器鎖(monitor)即是括號括起來的對象實例;
更加詳細的解析看[Java併發之Synchronized]()
理解了Volatile與Synchronized後,那咱們來看看如何使用Volatile與Synchronized優化單例模式
先來看看通常模式的單例模式:
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); // 建立實例 } return singleton; } }
可能出現問題:當有兩個線程A和B,
if(singleton == null)
準備執行建立實例時,線程掛起,首先想到是那就在使用synchronized做用在靜態方法:
public class Singleton { private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
雖然這樣簡單粗暴解決,但會致使這個方法比較效率低效,致使程序性能嚴重降低,那是否是還有其餘更優的解決方案呢?
能夠進一步優化建立了實例以後,線程再同步鎖以前檢驗singleton非空就會直接返回對象引用,而不用每次都在同步代碼塊中進行非空驗證,
若是隻有synchronized前加一個singleton非空,就會出現第一種狀況多個線程同時執行到條件判斷語句時,會建立多個實例
所以須要在synchronized後加一個singleton非空,就不會出現會建立多個實例,
class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null) singleton = new Singleton(); } } return singleton; } }
這個優化方案雖然解決了只建立單個實例,因爲存在着指令重排,會致使在多線程下也是不安全的(當發生了重排後,後續的線程發現singleton不是null而直接使用的時候,就會出現意料以外的問題。)。致使緣由singleton = new Singleton()
新建對象會經歷三個步驟:
因爲重排序的緣故,步驟二、3可能會發生重排序,其過程以下:
那麼問題找到了,那怎麼去解決呢?那就禁止不容許初始化階段步驟2 、3發生重排序,恰好Volatile 禁止指令重排,從而使得雙重檢測真正發揮做用。
public class Singleton { //經過volatile關鍵字來確保安全 private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
最終咱們這個完美的雙重檢測單例模式出來了
各位看官還能夠嗎?喜歡的話,動動手指點個💗,點個關注唄!!謝謝支持!
歡迎關注公衆號【Ccww技術博客】,原創技術文章第一時間推出