一、餓漢式
1、在static屬性中實例化(類加載的初始化階段實例化(在准備階段分配內存))
2、在static代碼塊中實例化(類加載的初始化階段實例化)
3、枚舉實現(https://www.cnblogs.com/yangyongjie/p/11056454.html)
二、懶漢式
1、同步方法或同步代碼塊
2、雙重檢查鎖
在Java多線程程序中,有時候需要采用延遲初始化來降低初始化類和創建對象的開銷,在使用這些對象時才進行初始化。延遲初始化需要注意線程安全
問題,否則就容易出現問題。
單例模式在獲取實例的方法中,若只判斷實例是否為null,是則創建對象,否則獲取對象。這種方法在多線程執行的時候必然會有線程安全問題。若獲取
實例方法加synchronized關鍵字,則能實現線程同步解決線程安全問題,但是多線程下頻繁調用會造成巨大的性能開銷。
1、雙重檢查鎖及其錯誤根源
雙重檢查鎖是常見的延遲初始化技術,初衷是用它來降低同步的開銷,但是它是錯誤的用法。
雙重檢查鎖代碼:
public class DoubleCheckLock { private static Instance instance; public static Instance getInstance(){ // 第一次檢查 if(instance==null){ // 第一次檢查為null再進行加鎖,降低同步帶來的性能開銷 synchronized (DoubleCheckLock.class){ // 第二次檢查 if(instance==null){ // 問題出在此處 instance=new Instance(); } } } return instance; } }
錯誤根源:
當第一次檢查時,讀取instance不為null時,instance引用的對象可能還沒有完成初始化!原因在於多線程下的重排序。
instance=new Instance() new創建一個對象不是原子操作,分為三步:
1)分配對象的內存空間
2)初始化對象
3)設置instance引用指向剛分配的內存地址
但是在一些編譯器上(如JIT),2)和3)可能會發生重排序。
Java規范保證了重排序不會改變單線程內的程序執行結果。但是在多線程下,若線程A在執行instance=new Instance();時發生了重排序,先執行了3),
這時候線程B剛好獲取到了instance不為null,接着去訪問對象。但是這個時候線程A還未執行2),即還沒被線程A初始化,那么這個時候線程B得到的就是
一個還沒有初始化的對象。
解決方案:
(1)不允許2)和3)重排序
(2)允許2)和3)重排序,但不允許其他線程“看到”這個重排序
2、基於volatile的解決方案
通過將instance聲明為volatile型來禁止2)和3)之間的重排序
public class VolatileDoubleCheckLock { // 將instance聲明為volatile型 private volatile static Instance instance; public static Instance getInstance(){ // 第一次檢查 if(instance==null){ // 第一次檢查為null再進行加鎖,降低同步帶來的性能開銷 synchronized (VolatileDoubleCheckLock.class){ // 第二次檢查 if(instance==null){ // 多線程下將禁止2)和3)之間的重排序 instance=new Instance(); } } } return instance; } }
3、基於類初始化的解決方案
JVM在類的初始化階段(即被Class加載后,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖(Class對象的初始化鎖)。這個鎖可以同步多個線程對一個類的初始化。
public class InstanceFactory { private static class InstanceHolder{ public static Instance instance=new Instance(); } public static Instance getInstance(){ return InstanceHolder.instance; } }
在多線程下,第一個執行getInstance方法的線程會先初始化InstanceHolder類。多個線程的情況下也只有一個線程能獲取到InstanceHolder類的Class對象的初始化鎖。第一個獲取到nstanceHolder類的Class對象的初始化鎖的線程將初始化InstanceHolder類中的靜態屬性instance。根據happens-before關系,其他線程將知道InstanceHolder類已被初始化,將結束初始化過程直接訪問InstanceHolder。