Java多线程之单例模式在多线程环境下的安全问题
Java多線程之單例模式在多線程環境下的安全問題
目錄:
1. 單例模式基本概念
基本概念轉載自:單例模式|菜鳥教程
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
注意:
意圖:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
主要解決:一個全局使用的類頻繁地創建與銷毀。
何時使用:當您想控制實例數目,節省系統資源的時候。
如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。
關鍵代碼:構造函數是私有的。
應用實例:
優點:
缺點:沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。
使用場景:
注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成 instance 被多次實例化。
2. 單線程下的單例模式
1. 單線程下單例模式代碼
public class SingletonDemo {private static SingletonDemo instance = null;private SingletonDemo(){System.out.println(Thread.currentThread().getName()+"\t 我是構造方法SingletonDemo");}public static SingletonDemo getInstance(){if (instance == null){instance = new SingletonDemo();}return instance;}public static void main(String[] args) {// 單線程(main線程的操作動作)System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());} }2. 編譯結果
3. 多線程下的單例模式
2.解決辦法,可以在getInstance()方法上加synchronized,但是不推薦。更好的解決辦法是使用DCL(Double Check Lock 雙端撿鎖機制)
代碼如下:
public class SingletonDemo {private static SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t 我是構造方法SingletonDemo");}//DCL (Double Check Lock 雙端撿鎖機制)public static SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class) {if (instance == null) {instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {//并發多線程后,情況發生了很大的變化for (int i = 1; i <= 20; i++) {new Thread(() -> {SingletonDemo.getInstance();}, String.valueOf(i)).start();}} }4. 單例模式volatile分析
代碼如下
public class SingletonDemo {private static volatile SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t 我是構造方法SingletonDemo");}//DCL (Double Check Lock 雙端撿鎖機制)public static SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class) {if (instance == null) {instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {//并發多線程后,情況發生了很大的變化for (int i = 1; i <= 20; i++) {new Thread(() -> {SingletonDemo.getInstance();}, String.valueOf(i)).start();}} }具體分析:
DCL(雙端檢鎖)機制不一定線程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排。
原因在于某一個線程執行到第一次檢測,讀取到的instance不為null時,instance的引用對象可能沒有完成初始化。
instance=newSingDemo();可以分為以下3步完成(偽代碼)
memory=allocate(): // 1.分配對象內存空間
instance(memory): // 2.初始化對象
instance=memory; //3. 設置instance指向剛分配的內存地址,此時instance != null
步驟2和步驟3不存在數據依賴關系,而且無論重排前還是重排后程序的執行結果在單線程中并沒有改變,因此這種重排優化是允許的。
memory=allocate();//1.分配對象內存空間
instance=memory;//3.設置ins怡nce指向剛分配的內存地址,此時instance != null,但是對象還沒有初始化完成!
instance(memory);//2.初始化對象
但是指令重排只會保證串行語義執行的一致性(單線程),但并不會關心多線程間的語義一致性。
所以當一個線程訪問instance不為null時,由于instance實例未必已初始化完成,也就造成了線程安全問題
總結
以上是生活随笔為你收集整理的Java多线程之单例模式在多线程环境下的安全问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java多线程之volatile详解
- 下一篇: Java多线程之CAS深入解析