什麼是發佈對象?
發佈對象是指使一個對象可以被當前範圍以外的代碼所使用安全
什麼是對象逸出?
對象逸出是一種錯誤的發佈,指當一個對象尚未構造完成時,就使它被其餘線程所見多線程
逸出-demo
@Slf4j public class Escape { private int thisCanBeEscape = 0; public Escape() { new InnerClass(); } private class InnerClass{ public InnerClass() { log.info("{}", Escape.this.thisCanBeEscape); } } public static void main(String[] args) { new Escape(); } }
在此實例中Escape對象尚未構造完成,就訪問了該對象的成員變量thisCanBeEscape,該類是線程不安全的,而且很是不推薦這麼寫。jvm
不安全的發佈-demo
@Slf4j public class UnsafePublish { private String[] states = {"a", "b", "c"}; public String[] getStates() { return states; } public static void main(String[] args) { UnsafePublish unsafePublish = new UnsafePublish(); log.info("{}", Arrays.toString(unsafePublish.getStates())); unsafePublish.getStates()[0] = "d"; log.info("{}", Arrays.toString(unsafePublish.getStates())); } }
輸出爲:[a,b,c]和[d,b,c] 這樣發佈的對象爲線程不安全的,由於沒法保證其餘線程是否會修改states域,從而形成狀態錯誤函數
安全發佈對象的四種方法:
a、在靜態初始化函數中初始化一個對象引用性能
b、將對象的引用保存到volatile類型域或AtomicReference對象中優化
c、將對象的引用保存到某個正確構造對象的final類型域中this
d、將對象的引用保存到一個由鎖保護的域中spa
如何安全的發佈一個對象呢?
咱們以對不一樣單例的實現,來講明一下安全發佈對象的方法線程
單例-demo1
public class SingletonExample1 { //私有構造函數 private SingletonExample1() { } //單例對象 private static SingletonExample1 instance = null; //靜態的工廠方法 public static SingletonExample1 getInstance() { //單線程沒有問題,多線程的時候會出現問題 if (instance == null) { instance = new SingletonExample1(); } return instance; } }
此實例在單線程模式下沒有任何問題,但在多線程模式下,如兩個線程都同時運行到判斷instance==null時,就有可能new出兩個實例來,因此說這是線程不安全的,這也就是懶漢模式,此實例知足了a條件,若是再加上d條件,在判斷是否爲null時加鎖,就能夠變爲線程安全的code
單例-demo2
public class SingletonExample2 { //餓漢模式不足,若是構造方法中有過多的處理,會致使類加載的時候特別慢 //私有構造函數 private SingletonExample2() { } //單例對象 private static SingletonExample2 instance = new SingletonExample2(); //靜態的工廠方法 public static SingletonExample2 getInstance() { return instance; } }
此demo是線程安全的,使用餓漢模式時須要注意兩點,一是此類確定被使用(避免形成資源浪費),二是私有的構造方法中沒有過多的處理4
單例-demo3
public class SingletonExample3 { //私有構造函數 private SingletonExample3() { } //單例對象 private static SingletonExample3 instance = null; //靜態的工廠方法 public static synchronized SingletonExample3 getInstance() { //單線程沒有問題,多線程的時候會出現問題 if (instance == null) { instance = new SingletonExample3(); } return instance; } }
此demo即爲demo1加鎖的狀況,是線程安全的,可是並不推薦這麼寫,由於這樣雖然保證了線程安全,但在性能上有必定的開銷
單例-demo4
/** * 懶漢模式 雙重同步鎖單例模式 * 單例實例在第一次使用時進行建立 * 雙重檢測機制不必定線程安全,由於有指令重排的存在 */ public class SingletonExample4 { //私有構造函數 private SingletonExample4() { } //單例對象 private static SingletonExample4 instance = null; //靜態的工廠方法 public static SingletonExample4 getInstance() { //單線程沒有問題,多線程的時候會出現問題 if (instance == null) { //雙重檢測機制 //同步鎖 synchronized (SingletonExample4.class) { if (instance == null) { instance = new SingletonExample4(); } } } return instance; } }
此demo是懶漢模式的優化版本,但注意此demo不是線程安全的,由於有指令重排的存在,當執行instance=new SingletonExample4()時,cpu會執行三步操做:一、memory=allocate() 分配對象的內存空間 二、ctorInstance()初始化對象 三、instance = memory 設置instance指向剛分配的內存, 可是因爲jvm和cpu的優化,會發生指令重排,如重排的結果變爲1,3,2,在單線程的狀況下沒有任何問題,可是在多線程的狀況下就可能發生問題,若是此時A線程執行到instance=new SingletonExample4(),發生了指令重排,執行到了第二步的3,此時instance已經執行了該對象的內存,可是該對象尚未初始化,若是在此時B線程正好執行到if(instance==null),此時該條件已經不成立,直接return,由於這個對象尚未初始化,直接去使用這個對象就可能發生問題。
單例-demo5
/** * 懶漢模式 雙重同步鎖單例模式 * 單例實例在第一次使用時進行建立 * 雙重檢測機制不必定線程安全,由於有指令重排的存在 */ public class SingletonExample5 { //私有構造函數 private SingletonExample5() { } //一、memory = allocate() 分配對象的內存空間 // 二、 ctorInstance() 初始化對象 // 三、 instance = memeory 設置instance指向剛分配的內存 //經過volatile和雙重檢測機制限制指令重排,volatile限制了代碼的寫操做 //單例對象,經過volatile限制代碼發生指令重排 private volatile static SingletonExample5 instance = null; //靜態的工廠方法 public static SingletonExample5 getInstance() { //單線程沒有問題,多線程的時候會出現問題 if (instance == null) { //雙重檢測機制 //同步鎖 synchronized (SingletonExample5.class) { if (instance == null) { instance = new SingletonExample5(); } } } return instance; } }
此demo是demo4的升級版,只要解決了指令重排問題,在上篇博客「線程安全性中」咱們已經介紹了volatile能夠限制代碼發生指令重排,此demo是線程安全的。
單例-demo6
/** * 餓漢模式 * 單例實例在類裝載時進行建立 */ @ThreadSafe public class SingletonExample6 { //餓漢模式不足,若是構造方法中有過多的處理,會致使類加載的時候特別慢 //私有構造函數 private SingletonExample6() { } //單例對象 靜態域的初始化 private static SingletonExample6 instance = null; //靜態塊方式 static { instance = new SingletonExample6(); } //靜態的工廠方法 public static SingletonExample6 getInstance() { return instance; } public static void main(String[] args) { System.out.println(getInstance().hashCode()); System.out.println(getInstance().hashCode()); } }
demo2是餓漢模式的靜態代碼域方式,此demo是餓漢模式的靜態代碼塊方式,此demo也是線程安全的
單例-demo7
/** * 枚舉模式:最安全 * 相比於懶漢模式在安全性方面更容易保證 * 相比於餓漢模式是在實際調用的時候才作最開始的初始化 */ public class SingletonExample7 { private SingletonExample7() { } //靜態的工廠方法 public static SingletonExample7 getInstance() { return Singleton.INSTANCE.getSingleton(); } private enum Singleton{ INSTANCE; private SingletonExample7 singleton; //JVM保證這個方法絕對只調用一次,且是在這個類調用以前初始化的 Singleton() { singleton = new SingletonExample7(); } public SingletonExample7 getSingleton() { return singleton; } } }
此demo是咱們最推薦的單例寫法,而且是線程安全的,它相比於懶漢模式在安全性方面更容易保證,相比於餓漢模式是在實際調用的時候才作最開始的初始化