Java線程安全的單例實(shí)現(xiàn)

1. 普通實(shí)現(xiàn)(線程不安全)

public class SingleDemo1 {

    private static SingleDemo1 instance = null;

    // 私有構(gòu)造
    private SingleDemo1() {
        // do something
    }

    /**
     * fixme: 線程不安全,可能會(huì)重復(fù)創(chuàng)建,導(dǎo)致實(shí)例被覆蓋
     */
    public static SingleDemo1 getInstance() {
        if (instance == null) {
            instance = new SingleDemo1();
        }
        return instance;
    }
}

2. 簡(jiǎn)單加鎖實(shí)現(xiàn)(鎖開(kāi)銷(xiāo)較大)

public class SingleDemo2 {

    private static SingleDemo2 instance = null;

    // 私有構(gòu)造
    private SingleDemo2() {
        // do something
    }

    /**
     * fixme: 每次獲取實(shí)例都要加鎖串行,開(kāi)銷(xiāo)較大
     */
    public static SingleDemo2 getInstance() {
        synchronized(SingleDemo2.class) {
            if (instance == null) {
                instance = new SingleDemo2();
            }
        }
        return instance;
    }
}

3. double check + synchronized

public class SingleDemo3 {

    private static SingleDemo3 instance = null;

    // 私有構(gòu)造
    private SingleDemo3() {
        // do something
    }

    public static SingleDemo3 getInstance() {
        if (instance == null) {
            synchronized(SingleDemo3.class) {
                if (instance == null) {
                    instance = new SingleDemo3();
                }
            }
        }
        return instance;
    }
}

內(nèi)外雙層檢查,外層存在競(jìng)態(tài),內(nèi)層可能會(huì)因?yàn)橹噶钪嘏艑?dǎo)致返回未初始化的對(duì)象。解決了可見(jiàn)性,但是未考慮有序性。

原始是因?yàn)椋瑢?code>instance = new SingleDemo3()這句轉(zhuǎn)化為偽代碼指令可表示為:

objRef = allocate(SingleDemo3.class); // 子操作1: 分配對(duì)象所需要的存儲(chǔ)空間
invokeConstructor(objRef); // 子操作2: 初始化objRef引用的對(duì)象
instance = objRef; // 子操作3: 將對(duì)象寫(xiě)入共享的變量

由于JIT編譯器可能將上述的子操作重排序?yàn)椋鹤硬僮鳍佟硬僮鳍邸硬僮鳍冢丛诔跏蓟瘜?duì)象之前將對(duì)象的引用寫(xiě)入實(shí)例變量instance。由于鎖對(duì)有序性的保障是有條件的,而操作①(第1次檢查)讀取instance變量的時(shí)候并沒(méi)有加鎖,因此上述重排序?qū)Σ僮鳍俚膱?zhí)行線程是有影響的:該線程可能看到一個(gè)未初始化(或未初始化完畢)的實(shí)例,即變量instance的值不為null,但是該變量所引用的對(duì)象中的某些實(shí)例變量的變量值可能仍然是默認(rèn)值,而不是構(gòu)造器中設(shè)置的初始值。也就是說(shuō),一個(gè)線程在執(zhí)行操作①的時(shí)候發(fā)現(xiàn)instance不為null,于是該線程就直接返回這個(gè)instance變量所引用的實(shí)例,而這個(gè)實(shí)例可能是未初始化完畢的,這就可能導(dǎo)致程序出錯(cuò)!

4. double check + synchronized + volatile

針對(duì)3中的實(shí)現(xiàn),只需要給共享變量instance添加volatile關(guān)鍵字即可。volatile關(guān)鍵字解決了兩個(gè)問(wèn)題:

  • 保障可見(jiàn)性:任意線程通過(guò)寫(xiě)操作修改了instance的指向,其它線程也可以讀取到對(duì)應(yīng)的修改。
  • 保障有序性:由于volatile關(guān)鍵字能夠禁止volatile變量寫(xiě)操作與該操作之前的任何讀寫(xiě)進(jìn)行重排序,因此就不存在將子操作2重排到子操作3之后的情況。
public class SingleDemo4 {

    // 添加volatile修飾
    private volatile static SingleDemo4 instance = null;

    // 私有構(gòu)造
    private SingleDemo4() {
        // do something
    }

    public static SingleDemo4 getInstance() {
        if (instance == null) {
            synchronized(SingleDemo4.class) {
                if (instance == null) {
                    instance = new SingleDemo4();
                }
            }
        }
        return instance;
    }
}

5. 靜態(tài)內(nèi)部類(lèi)

除了上面的方式之外,還有其它的單例實(shí)現(xiàn)模式,比如靜態(tài)內(nèi)部的方式實(shí)現(xiàn)。

當(dāng)外部調(diào)用getInstance方法時(shí),Inner內(nèi)部類(lèi)會(huì)被jvm加載,由于instance是靜態(tài)變量,所以會(huì)被一同初始化,以此達(dá)到了lazy初始化的同時(shí)也保證了線程安全。

public class SingleDemo5 {

    private static class Inner {
        final static SingleDemo5 instance = new SingleDemo5();
    }

    // 私有構(gòu)造
    private SingleDemo5() {
        // do something
    }

    public static SingleDemo5 getInstance() {
        return Inner.instance;
    }
}

6. 枚舉實(shí)現(xiàn)

枚舉類(lèi)型SingleDemo6相當(dāng)于一個(gè)單例類(lèi),其字段INSTANCE值相當(dāng)于該類(lèi)的唯一實(shí)例。這個(gè)實(shí)例是在SingleDemo6.INSTANCE初次被引用的時(shí)候才被初始化的。僅訪問(wèn)SingleDemo6本身(比如上述的SingleDemo6.class.getName()調(diào)用)并不會(huì)導(dǎo)致SingleDemo6的唯一實(shí)例被初始化。

public enum SingleDemo6 {

    INSTANCE;

    // 私有構(gòu)造
    SingleDemo6() {
        // init
    }
    
    // 外部通過(guò)枚舉實(shí)例訪問(wèn)即可
    public void doSomething(){
        // do something
    }
}

引用自:黃文海. Java多線程編程實(shí)戰(zhàn)指南(核心篇) (Java多線程編程實(shí)戰(zhàn)系列) (Chinese Edition) (Kindle Locations 2435-2437). 電子工業(yè)出版社. Kindle Edition.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。