线程安全的几种单例模式
單例模式
單例模式是 Java 中常用的設計模式之一,屬于設計模式三大類中的創(chuàng)建型模式。在運行期間,保證某個類僅有一個實例,并提供一個訪問它的全局訪問點。單例模式所屬類的構造方法是私有的,所以單例類是不能被繼承的。實現線程安全的單例模式有以下幾種方式:
1.餓漢式
public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}}這是實現一個安全的單例模式的最簡單粗暴的寫法,之所以稱之為餓漢式,是因為肚子餓了,想馬上吃到東西,不想等待生產時間。在類被加載的時候就把Singleton實例給創(chuàng)建出來供使用,以后不再改變。
優(yōu)點:實現簡單,?線程安全,調用效率高(無鎖,且對象在類加載時就已創(chuàng)建,可直接使用)。
缺點:可能在還不需要此實例的時候就已經把實例創(chuàng)建出來了,不能延時加載(在需要的時候才創(chuàng)建對象)。
2.懶漢式
public class Singleton {private static Singleton instance = null;private Singleton() {}//如果沒有synchronized,則線程不安全public static synchronized Singleton getInstance() {//synchronized也可以寫在方法里,形成同步代碼塊if (instance == null) {instance = new Singleton();}return instance;}}相比餓漢式,懶漢式顯得沒那么“餓”,在真正需要的時候再去創(chuàng)建實例。
優(yōu)點:線程安全,可以延時加載。
缺點:調用效率不高(有鎖,且需要先創(chuàng)建對象)。
3.懶漢式改良版(雙重同步鎖)
public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;} }使用了double-check,即check-加鎖-check,減少了同步的開銷
第2種懶漢式的效率低在哪里呢?第二種寫法將synchronized加在了方法上(或者寫在方法里),在單例對象被創(chuàng)建后,因為方法加了鎖,所以要等當前線程得到對象釋放鎖后,下一個線程才可以進入getInstance()方法獲取對象,也就是線程要一個一個的去獲取對象。而采用雙重同步鎖,在synchronized代碼塊前加了一層判斷,這使得在對象被創(chuàng)建之后,多線程不需進入synchronized代碼塊中,可以多線程同時并發(fā)訪問獲取對象,這樣效率大大提高。
在創(chuàng)建第一個對象時候,可能會有線程1,線程2兩個線程進入getInstance()方法,這時對象還未被創(chuàng)建,所以都通過第一層check。接下來的synchronized鎖只有一個線程可以進入,假設線程1進入,線程2等待。線程1進入后,由于對象還未被創(chuàng)建,所以通過第二層check并創(chuàng)建好對象,由于對象singleton是被volatile修飾的,所以在對singleton修改后會立即將singleton的值從其工作內存刷回到主內存以保證其它線程的可見性。線程1結束后線程2進入synchronized代碼塊,由于線程1已經創(chuàng)建好對象并將對象值刷回到主內存,所以這時線程2看到的singleton對象不再為空,因此通過第二層check,最后獲取到對象。這里volatile的作用是保證可見性,同時也禁止指令重排序,因為上述代碼中存在控制依賴,多線程中對控制依賴進行指令重排序會導致線程不安全。
優(yōu)點:線程安全,可以延時加載,調用效率比2高。
4.內部靜態(tài)類
public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance = new Singleton();}}靜態(tài)內部類只有被主動調用的時候,JVM才會去加載這個靜態(tài)內部類。外部類初次加載,會初始化靜態(tài)變量、靜態(tài)代碼塊、靜態(tài)方法,但不會加載內部類和靜態(tài)內部類。
優(yōu)點:線程安全,調用效率高,可以延時加載。
?
似乎靜態(tài)內部類看起來已經是最完美的方法了,其實不是,可能還存在反射攻擊和反序列化攻擊。
a)反射攻擊
public static void main(String[] args) throws Exception {Singleton singleton = Singleton.getInstance();Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton newSingleton = constructor.newInstance();System.out.println(singleton == newSingleton); }運行結果:false
通過結果看,這兩個實例不是同一個,違背了單例模式的原則。
b)反序列化攻擊
引入依賴:
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8.1</version> </dependency>這個依賴提供了序列化和反序列化工具類。
Singleton類實現java.io.Serializable接口。
public class Singleton implements Serializable {private static class SingletonHolder {private static Singleton instance = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.instance;}public static void main(String[] args) {Singleton instance = Singleton.getInstance();byte[] serialize = SerializationUtils.serialize(instance);Singleton newInstance = SerializationUtils.deserialize(serialize);System.out.println(instance == newInstance);}}運行結果:false
?
5.枚舉
最佳的單例實現模式就是枚舉模式。寫法簡單,線程安全,調用效率高,可以天然的防止反射和反序列化調用,不能延時加載。
public enum Singleton {INSTANCE;public void doSomething() {System.out.println("doSomething");}}調用方法:
public class Main {public static void main(String[] args) {Singleton.INSTANCE.doSomething();}}直接通過Singleton.INSTANCE.doSomething()的方式調用即可。
枚舉如何實現線程安全?反編譯后可以發(fā)現,會通過一個類去繼承改枚舉,然后通過靜態(tài)代碼塊的方式在類加載時實例化對象,與餓漢式類似。https://blog.csdn.net/wufaliang003/article/details/81395411
如何做到防止反序列化調用?每一個枚舉類型及其定義的枚舉變量在JVM中都是唯一的,Java做了特殊的規(guī)定,枚舉類型序列化和反序列化出來的是同一個對象。
除此之外,枚舉還可以防止反射調用。
?
綜上,線程安全的幾種單例模式比較來看:
枚舉(無鎖,調用效率高,可以防止反射和反序列化調用,不能延時加載)> 靜態(tài)內部類(無鎖,調用效率高,可以延時加載)?> 雙重同步鎖(有鎖,調用效率高于懶漢式,可以延時加載) > 懶漢式(有鎖,調用效率不高,可以延時加載) ≈?餓漢式(無鎖,調用效率高,不能延時加載)
ps:只有枚舉能防止反射和反序列化調用
?
?
總結
以上是生活随笔為你收集整理的线程安全的几种单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米手机无法连上WIFI网络的解决方案
- 下一篇: c语言基础知识_结构体访问,共同体,枚举