優雅的手寫一個線程安全的單例模式

單例模式是我們非常常用的設計模式之一。百度百科給出的定義:

單例模式,屬於創建類型的一種常用的軟件設計模式。通過單例模式的方法創建的類在當前進程中只有一個實例(根據需要,也有可能一個線程中屬於單例,如:僅線程上下文內使用同一個實例)

一般,我們爲了實現單例模式,通常把構造器私有化,然後通過靜態方法和靜態變量來獲取一個對象。

懶漢式,線程不安全

這種方式是最基礎的實現方式,最大的問題是不支持多線程,在多線程模式下會產生線程安全問題。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

所以我們給getInstance()加synchronized鎖,保證它在多線程的情況下使用。但會影響效率。

public class Singleton {

    private Singleton singleton;
    private Singleton(){};
    public synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

餓漢式

比較常用的一種方法,使用classLoder機制避免了多線程同步問題。但是會產生垃圾對象,浪費內存。

public class Singleton {

    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance() {
        return instance;
    }

}

雙重校驗鎖

採用懶加載模式,同樣也是線程安全的。採用雙鎖機制,在多線程模式下也能保持高性能

/**
 * 懶漢式單例模式(線程不安全)
 * */
public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

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

使用volatile關鍵字

上面雙重校驗鎖可能會出現如下線程問題:
在 Singleton 構造函數體執行之前,變量 instance 可能成爲非 null 的,即賦值語句在對象實例化之前調用,此時別的線程得到的是一個還會初始化的對象, 也就是 一個線程走到3正在初始化時被掛起,另一個線程走到0,發現instance不爲null,就直接返回一個沒有初始化完全的對象!
所以爲了避免這種情況發生,我們給instance加上volatile關鍵字,利用它禁止指令重排序的功能,給寫操作添加一個內存屏障。即在完全的初始化完一個對象前,不會調用讀操作。

public class Singleton {

    private volatile static Singleton singleton;

    private Singleton() {}

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

靜態內部類登記

和餓漢式同樣,使用ClassLoader機制來保證了初始化instance只有一個線程。而和餓漢式不一樣的地方在於,使用了Lazy初始化,只有在主動調用getInstance方法時,纔會實例化對象。

/**
 * 懶漢式單例模式(線程不安全)
 * */
public class Singleton {

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

}

枚舉

採用單例模式的最佳方法,簡介、自動支持序列化機制,而且避免了線程同步問題。不過目前很少被使用。

public enum  Singleton {
    INSTANCE;

    public void whateverMethod() {}

}

使用ThreadLocal實現單例模式(線程安全)

ThreadLocal爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。

/**
 * 使用ThreadLocal實現單例模式(線程安全)
 * */
public class Singleton {

    private static final ThreadLocal<Singleton> tlSingleton = new ThreadLocal<Singleton>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };

    private Singleton(){}

    public static Singleton getInstance() {
        return tlSingleton.get();
    }

}

使用CAS鎖實現

import java.util.concurrent.atomic.AtomicReference;

/**
 * CAS
 * */
public class Singleton {

    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();

    private Singleton(){}

    public static Singleton getInstance() {
        for (;;) {
            Singleton current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Singleton();
            if (INSTANCE.compareAndSet(null , current)) {
                return current;
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章