1. 程式人生 > >驀然回頭-單例模式篇章二

驀然回頭-單例模式篇章二

單例模式引發相關整理

關聯執行緒安全

在多執行緒下,懶漢式會有一定修改。當兩個執行緒在if(null == instance)語句阻塞的時候,可能由兩個執行緒進入建立例項,從而返回了兩個物件。對此,我們可以加鎖,保證僅有一個執行緒處於getInstance()方法中,從而保證了執行緒一致性。多執行緒下的單例

/**
 * @author sunyang
 * @date 2018/11/8 12:18
 */
public class Singleton4 {

    private static
Singleton4 instance; private Singleton4(){ } //需要加上synchronized 同步 public static synchronized Singleton4 getInstance(){ if (instance == null){ instance = new Singleton4(); } return instance; } }

如果一個專案中有100次獲取例項,那麼jvm就會有100次進行加鎖,釋放鎖的操作,每次操作都浪費資源。

可以在最外層再加一層判斷,如下

/**
 * @author sunyang
 * @date 2018/11/12 19:07
 */
public class Singleton41 {
    private static Singleton41 instance;

    private Singleton41(){}

    private static synchronized void doGetInstance(){
        if (null == instance){
            instance = new Singleton41();
        }
    }

    public
static synchronized Singleton41 getInstance()
{ if (null == instance){ doGetInstance(); } return instance; } }

簡化形式後:

/**
 * @author sunyang
 * @date 2018/11/12 19:13
 */
public class Singleton5 {

    private static Singleton5 instance;

    private Singleton5(){}

    //如果多個執行緒同時通過第一次檢查,並且其中一個執行緒
    // 首先通過了第二次檢查並例項化了物件,那麼剩餘通過了
    //第一次檢查的執行緒就不會再去例項化物件。提升了效率
    public static Singleton5 getInstance(){
        if (null == instance){
            synchronized (Singleton5.class){
                if (null == instance){
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

題外話:一個類在new的時候,一般經歷以下三個順序:

1.開闢空間

2.符號引用改空間,並在空間內對類進行初始化操作

3.將符合引用轉為直接引用這個時候if(null==instance) return false;


在實際的情況中,為了降低CPU的閒置時間,jvm會對指令進行重排序以形成指令流水線。順序可能亂序:

1.開闢空間

2.轉為直接引用

3.初始化類

結論:雙重檢查機制就會出現問題:可能返回一個未被完全初始化的類;

程式碼不安全截圖

 

1542025736992
1542025736992

 

volatile單例中的作用
  • 可見性:jvm中每一個執行緒都有自己的記憶體區域。對變數使用volatile修飾,可以強制將每一次的讀寫都寫入堆記憶體中,實現了各個執行緒都能共享的最新資料。
  • 禁止指令重排序優化:被volatile修飾的變數,在賦值的結尾會插入一個記憶體屏障,從而防止指令重排序。volatile增強了資料的一致性。
/**
 * 解決上圖雙重檢查機制出現的問題,可能返回一個未被完全初始化的類
 *
 * @author sunyang
 * @date 2018/11/12 20:24
 */
public class Singleton51 {

    private static volatile Singleton51 instance;

    private Singleton51(){}
    public static Singleton51 getInstance(){
        if (null==instance){
            synchronized (Singleton51.class){
                if (null == instance){
                    instance = new Singleton51();
                }
            }
        }
        return instance;
    }
}
如果實現懶載入

先了解下靜態巢狀類的使用

靜態巢狀類:是一種在類之外宣告的巢狀類,由於是靜態的,所以不經過初始化,就可以通過類名直接呼叫。

內部類:該類作為另一個類的成員,因此只有引用另一個類,才能建立這個類。通過靜態巢狀類,便可以實現

對餓漢式進行懶化的效果。

/**
 * @author sunyang
 * @date 2018/11/12 20:42
 */
public class Singleton6 {
    private Singleton6(){}
    //靜態內部類
    private static class SingletonHolder{
        private static Singleton6 INSTANCE = new Singleton6();
    }

    //通過靜態巢狀類,便可以實現對餓漢式進行懶化的效果
    public static final Singleton6 getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
分析

要分析這種方式有沒有實現懶載入,就要分析一下語句new Singleton6()是什麼時候被呼叫的.

使用javac進行編譯,會得到如下圖的三個class檔案:

 

1542027696077
1542027696077

 

從圖上可以看到,靜態巢狀類是單獨作為一個class存在,而其中建立物件的邏輯位於巢狀類中,jvm讀取巢狀類的位元組碼以後才能建立物件,從硬碟中讀取class檔案,在記憶體中分配空間,是一件費事費力的工作,所以jvm選擇按需載入,沒有必要載入的就不載入,沒必要分配就不分配。