java 的知識要多多了解。(下文為轉發)
什麽是發布對象?
發布對象是指使一個對象能夠被當前範圍之外的代碼所使用
什麽是對象逸出?
對象逸出是一種錯誤的發布,指當一個對象還沒有構造完成時,就使它被其他線程所見
逸出-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,該類是線程不安全的,並且非常不推薦這麽寫。
不安全的發布-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類型域中
d、將對象的引用保存到一個由鎖保護的域中
如何安全的發布一個對象呢?
我們以對不同單例的實現,來說明一下安全發布對象的方法
單例-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時加鎖,就可以變為線程安全的
單例-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會執行三步操作:1、memory=allocate() 分配對象的內存空間 2、ctorInstance()初始化對象 3、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() { } //1、memory = allocate() 分配對象的內存空間 // 2、 ctorInstance() 初始化對象 // 3、 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是我們最推薦的單例寫法,並且是線程安全的,它相比於懶漢模式在安全性方面更容易保證,相比於餓漢模式是在實際調用的時候才做最開始的初始化
java 的知識要多多了解。(下文為轉發)