阿里面試官:如何實現一個線程安全的單例,前提是不能加鎖

單例,大家肯定都不陌生,這是Java中很重要的一個設計模式。稍微瞭解一點單例的朋友也都知道實現單例是要考慮併發問題的,一般情況下,我們都會使用synchronized來保證線程安全。

那麼,如果有這樣一道面試題:不使用synchronized和lock,如何實現一個線程安全的單例?你該如何回答?

C類應聘者:可以使用餓漢模式實現單例。如:

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton (){}

    public static Singleton getInstance() {

      return instance;

    }

}

還有部分程序員可以想到餓漢的變種:

public class Singleton {

    private Singleton instance = null;

    static {

instance = new Singleton();

    }

    private Singleton (){}

    public static Singleton getInstance() {

        return this.instance;

    }

}

使用static來定義靜態成員變量或靜態代碼,藉助Class的類加載機制實現線程安全單例。

面試官:除了這種以外,還有其他方式嗎?

B類應聘者:

除了以上兩種方式,還有一種辦法,就是通過靜態內部類來實現,代碼如下:

public class Singleton {

    private static class SingletonHolder {

    private static final Singleton INSTANCE = new Singleton();

}

private Singleton (){}

public static final Singleton getInstance() {

return SingletonHolder.INSTANCE;

}

}

這種方式相比前面兩種有所優化,就是使用了lazy-loading。Singleton類被裝載了,但是instance並沒有立即初始化。因爲SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,纔會顯示裝載SingletonHolder類,從而實例化instance。

面試官:除了這種以外,還有其他方式嗎?

A類應聘者:

除了以上方式,還可以使用枚舉的方式,如:

public enum Singleton {

INSTANCE;

    public void whateverMethod() {

}

}

這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,可謂是很堅強的壁壘。

面試官:以上幾種答案,其實現原理都是利用藉助了類加載的時候初始化單例。即藉助了ClassLoader的線程安全機制。

所謂ClassLoader的線程安全機制,就是ClassLoader的loadClass方法在加載類的時候使用了synchronized關鍵字。也正是因爲這樣, 除非被重寫,這個方法默認在整個裝載過程中都是同步的,也就是保證了線程安全。

所以,以上各種方法,雖然並沒有顯示的使用synchronized,但是還是其底層實現原理還是用到了synchronized。

面試官:除了這種以外,還有其他方式嗎?

A類應聘者:

還可以使用Java併發包中的Lock實現

面試官:本質上還是在使用鎖,不使用鎖的話,有辦法實現線程安全的單例嗎?

A+類面試者:

有的,那就是使用CAS。

CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。實現單例的方式如下:

public class Singleton {

    private static final AtomicReference INSTANCE = new AtomicReference();

    private Singleton() {}

    public static Singleton getInstance() {

        for (;;) {

Singleton singleton = INSTANCE.get();

            if (null != singleton) {

                return singleton;

            }

singleton = new Singleton();

            if (INSTANCE.compareAndSet(null, singleton)) {

                return singleton;

            }

        }

    }

}

面試官:這種方式實現的單例有啥優缺點嗎?

A++類面試者:

用CAS的好處在於不需要使用傳統的鎖機制來保證線程安全,CAS是一種基於忙等待的算法,依賴底層硬件的實現,相對於鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的並行度。

CAS的一個重要缺點在於如果忙等待一直執行不成功(一直在死循環中),會對CPU造成較大的執行開銷。

另外,如果N個線程同時執行到singleton = new Singleton();的時候,會有大量對象創建,很可能導致內存溢出。

面試官:你被錄取了!

------END

最後附上筆者創建的一個java技術交流羣,歡迎大家進羣交流java相關的技術,羣主會不定時發紅包,組織抽獎,獎品是下面幾本書之一:

從paxos到zookeeper分佈式一致性原理與實踐    作者:倪超

Redis設計與實現    作者:黃建宏

kafka源碼分析   

分佈式系統架構設計與實現

高性能mysql

Innodb引擎原理分析

還有幾本,篇幅限制就不一一列舉了



作者:名猿
鏈接:https://www.jianshu.com/p/f3fae8658f13
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章