單例模式(思惟導圖)

圖1 Java單例模式【點擊查看大圖】java

  單例模式:【核心】只存在一個實例化對象編程

  單例模式確保某個類只有一個實例,並且自行實例化並向整個系統提供這個實例。在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、顯卡的驅動程序對象常被設計成單例。這些應用都或多或少具備資源管理器的功能。每臺計算機能夠有若干個打印機,但只能有一個Printer Spooler,以免兩個打印做業同時輸出到打印機中。每臺計算機能夠有若干通訊端口,系統應當集中管理這些通訊端口,以免一個通訊端口同時被兩個請求同時調用。總之,選擇單例模式就是爲了不不一致狀態,避免政出多頭。設計模式

  Singleton經過將構造方法限定爲private避免了類在外部被實例化,在同一個虛擬機範圍內,Singleton的惟一實例只能經過getInstance()方法訪問。緩存

  (事實上,經過Java反射機制是可以實例化構造方法爲private的類的,那基本上會使全部的Java單例實現失效。此問題在此處不作討論,姑且掩耳盜鈴地認爲反射機制不存在。)

安全

1,餓漢式

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Singleton {
 4     private static Singleton instance=new Singleton();
 5     private Singleton(){
 6         System.out.println("餓漢式 無參構造");
 7     }
 8     public static Singleton getInstance(){
 9         return instance;
10     }
11     public void print(){
12         System.out.println("餓漢式【單例設計模式】");
13     }
14 }
View Code

調用就直接進行實例化多線程

單例調用測試:併發

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Demo {
 4     public static void main(String[] args) throws Exception {
 5         long startTime =  System.currentTimeMillis();
 6         //多線程狀況下
 7         for(int x=0;x<1000;x++){
 8             new Thread(()->{
 9                 Singleton.getInstance().print();
10             },"單例消費端-"+x).start();
11         }
12 
13         long endTime =  System.currentTimeMillis();
14         long usedTime = (endTime-startTime);
15         System.out.println("耗時:"+usedTime);//143
16 
17     }
18 }
View Code

 

2,普通懶漢式【非線程安全

 1 package com.cnblogs.mufasa.Demo3;
 2 //普通懶漢式,多線程會出現多個實例化
 3 //多線程狀況下,就不是單例模式了,沒有進行synchronized
 4 public class Singleton1 {
 5     private static Singleton1 instance=null;
 6     private String str="";
 7     private Singleton1(){
 8         System.out.println("【"+Thread.currentThread().getName()+"】單線程下的單例模式");
 9     }
10     public static Singleton1 getInstance(){
11         if(instance==null){
12             instance=new Singleton1();
13         }
14         return instance;
15     }
16     public void print(){
17         System.out.println(str+"懶漢式單例模式");
18     }
19 
20     public String getStr() {
21         return str;
22     }
23 
24     public void setStr(String str) {
25         this.str = str;
26     }
27 }
View Code

 須要的時候才進行實例化,多線程的時候會出現實例化多個對象的狀況!!!框架

3,初級同步懶漢式

 1 package com.cnblogs.mufasa.Demo3;
 2 //代價很大,之前的併發變成串行,效率下降
 3 public class Singleton2 {
 4     private static Singleton2 instance = null;
 5     private Singleton2(){
 6         System.out.println("【"+Thread.currentThread().getName()+"】單線程下的單例模式");
 7     }
 8     public static synchronized Singleton2 getInstance(){
 9         if(instance==null){
10             instance=new Singleton2();
11         }
12         return instance;
13     }
14     public void print(){
15         System.out.println("懶漢式【單例設計模式】添加getInstance同步修飾");
16     }
17 }
View Code

 對getInstance進行synchronized修飾,可是這個把本來的併發操做進行串行轉換,低效ide

4,高級同步懶漢式【無volatile修飾】

 1 package com.cnblogs.mufasa.Demo3;
 2 //雙重效驗鎖
 3 //只是在處理空對象時,才進行同步操做,只有一次是同步其餘所有是併發非同步處理
 4 public class Singleton3 {
 5 //    private static volatile Singleton3 instance=null;
 6     private static Singleton3 instance = null;//添加volatile修飾符
 7     private Singleton3(){
 8         System.out.println("【"+Thread.currentThread().getName()+"】*** 單線程下的【單例模式】 ***");
 9     }
10     public static Singleton3 getInstance(){//仍是併發處理
11         if(instance==null){
12             synchronized (Singleton3.class){
13                 if(instance==null){
14                     instance=new Singleton3();
15                 }
16             }
17         }
18         return instance;
19     }
20     public void print(){
21         System.out.println("懶漢式【單例設計模式】");
22     }
23 }
View Code

 也叫作雙重效驗鎖,進行兩次判斷,而且第二次以前進行同步操做處理測試

接下來我解釋一下在併發時,雙重校驗鎖法會有怎樣的情景:

STEP 1. 線程A訪問getInstance()方法,由於單例尚未實例化,因此進入了鎖定塊。

STEP 2. 線程B訪問getInstance()方法,由於單例尚未實例化,得以訪問接下來代碼塊,而接下來代碼塊已經被線程1鎖定。

STEP 3. 線程A進入下一判斷,由於單例尚未實例化,因此進行單例實例化,成功實例化後退出代碼塊,解除鎖定。

STEP 4. 線程B進入接下來代碼塊,鎖定線程,進入下一判斷,由於已經實例化,退出代碼塊,解除鎖定。

STEP 5. 線程A獲取到了單例實例並返回,線程B沒有獲取到單例並返回Null

5,volatile修飾懶漢式

 1 package com.cnblogs.mufasa.Demo3;
 2 //雙重效驗鎖
 3 //只是在處理空對象時,才進行同步操做,只有一次是同步其餘所有是併發非同步處理
 4 public class Singleton3 {
 5     private static volatile Singleton3 instance=null;
 6 //    private static Singleton3 instance = null;//添加volatile修飾符
 7     private Singleton3(){
 8         System.out.println("【"+Thread.currentThread().getName()+"】*** 單線程下的【單例模式】 ***");
 9     }
10     public static Singleton3 getInstance(){//仍是併發處理
11         if(instance==null){
12             synchronized (Singleton3.class){
13                 if(instance==null){
14                     instance=new Singleton3();
15                 }
16             }
17         }
18         return instance;
19     }
20     public void print(){
21         System.out.println("懶漢式【單例設計模式】");
22     }
23 }
View Code

 保持內存可見性和防止指令重排序,直接跳過副本對內存進行操做

6,Holder式

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Singleton4 {
 4     private static class SingletonHolder{
 5         private static Singleton4 instance=new Singleton4();
 6     }
 7     private Singleton4(){
 8         System.out.println("無參構造");
 9     }
10     public static Singleton4 getInstance(){
11         return SingletonHolder.instance;
12     }
13     public void print(){
14         System.out.println("holder模式實現的線程安全單例"+Thread.currentThread().getName());
15     }
16 }
View Code

 靜態類內部加載,經過調用內部靜態類——內部靜態中的靜態屬性初始化——來建立對象

7,枚舉法實現

1 package com.cnblogs.mufasa.Demo3;
2 
3 enum  Singleton5 {
4     INSTANCE;
5     public void print(){
6         System.out.println("枚舉法實現的線程安全單例模式"+Thread.currentThread().getName());
7     }
8 }
View Code

 利用枚舉自己特性實現,(1)自由串行化。(2)保證只有一個實例。(3)線程安全。

枚舉與其餘的單例調用方式不太同樣:

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 public class Demo5 {
 4     public static void main(String[] args) {
 5         long startTime =  System.currentTimeMillis();
 6         //多線程狀況下
 7         for(int x=0;x<1000;x++){
 8             new Thread(()->{
 9                 Singleton5.INSTANCE.print();
10             },"單例消費端-"+x).start();
11         }
12         long endTime =  System.currentTimeMillis();
13         long usedTime = (endTime-startTime);
14 
15         System.out.println("耗時:"+usedTime);//144
16     }
17 }
View Code

 

8,ThreadLocal實現

 1 package com.cnblogs.mufasa.Demo3;
 2 //使用ThreadLocal實現單例模式(線程安全)
 3 public class Singleton6 {
 4     private Singleton6(){}
 5     private static final ThreadLocal<Singleton6> tlSingleton=new ThreadLocal<>(){
 6         @Override
 7         protected Singleton6 initialValue(){
 8             return new Singleton6();
 9         }
10     };
11 
12     public static Singleton6 getInstance(){
13         return tlSingleton.get();
14     }
15 
16     public static void print(){
17         System.out.println("ThreadLocal實現單例模式");
18     }
19 
20 }
View Code

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

經過犧牲空間換取時間——原始數據是一樣的可是咱們不使用原始數據,咱們使用它的拷貝副本,避免各個線程爭搶資源【之前有一個球你們都搶來搶去,如今給你們夥每人發一個,你們都不搶了】!有點像原型模式的策略

9,CAS鎖實現

 1 package com.cnblogs.mufasa.Demo3;
 2 
 3 import java.util.concurrent.atomic.AtomicReference;
 4 
 5 //使用CAS鎖實現(線程安全)
 6 public class Singleton7 {
 7     private static final AtomicReference<Singleton7> INSTANCE = new AtomicReference<Singleton7>();
 8     private Singleton7(){
 9     }
10     public static final Singleton7 getInstance(){
11         for(;;){
12             Singleton7 current=INSTANCE.get();
13             if(current!=null){
14                 return current;
15             }
16             current=new Singleton7();
17             if(INSTANCE.compareAndSet(null,current)){
18                 System.out.println("同步鎖比較");
19                 return current;
20             }
21         }
22     }
23     public static void print(){
24         System.out.println("CAS鎖實現單例模式");
25     }
26 
27 }
View Code

 經過編程的原子操做來進行同步

(1)使用總線鎖保證原子性【一個CPU操做共享變量內存地址的緩存的時候,鎖住總線其餘CPU不能插手——變成串行化操做,一個個的來】

(2)使用緩存鎖保證原子性【內存區域若是被緩存在處理器的緩存行中,而且在Lock操做期間被鎖定,那麼當它執行鎖操做回寫到內存時,處理器不在總線上聲言LOCK#信號,而是修改內部的內存地址,並容許它的緩存一致性機制來保證操做的原子性,由於緩存一致性機制會阻止同時修改由兩個以上處理器緩存的內存區域數據,當其餘處理器回寫已被鎖定的緩存行的數據時,會使緩存行無效】

(3)Java使用循環CAS實現原子操做

https://blog.csdn.net/zxx901221/article/details/83033998

 

 10,登記式單例

  Spring的IOC-實現中就使用的在這種策略,Definition中緩存的的實例,下次再使用的時候直接調用便可。

package com.cnblogs.mufasa.Demo3;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Singleton8 {
    private static Map<String,Singleton8> map=new HashMap<String, Singleton8>();
    static {
        Singleton8 singleton=new Singleton8();
        map.put(singleton.getClass().getName(),singleton);
    }
    protected Singleton8(){}
    public static Singleton8 getInstance(String name){
        if(name==null){
            name=Singleton8.class.getName();
            System.out.println("name == null"+"--->name="+name);
        }
        if(map.get(name)==null){
            try {
                map.put(name,(Singleton8) Class.forName(name).getDeclaredConstructor().newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
    public void print(){
        System.out.println("利用反射&類名註冊機制實現單例");
    }
}
View Code

利用反射和類名註冊實現的單例模式

package com.cnblogs.mufasa.Demo3;

public class Demo8 {
    public static void main(String[] args) {
        long startTime =  System.currentTimeMillis();
        //多線程狀況下
        for(int x=0;x<1000;x++){
            new Thread(()->{
                Singleton8.getInstance("com.cnblogs.mufasa.Demo3.Singleton8").print();
            },"單例消費端-"+x).start();
        }
        long endTime =  System.currentTimeMillis();
        long usedTime = (endTime-startTime);

        System.out.println("耗時:"+usedTime);//143
    }
}
View Code

 

 11,總結

請編寫單例設計模式

·【100%】直接編寫一個餓漢式的單例模式,而且實現構造方法私有化;

·【120%】在Java中哪裏使用到單例設計模式?Runtime類、Pattern類、Spring框架;

·【200%】懶漢式單例設計模式問題?多線程狀況下,可能產生多個實例化對象,違法了其單例的初衷!

 

12,如何破解單例模式?

單例模式是隻能產生一個實例化對象,構造方法私有化,不能經過普通的方法進行實例化。

  若是想要獲取新的實例化對象,要怎麼辦呢?

  ①直接跳過無視私有化構造:反射機制

  ②我壓根不新創建一個實例化對象,跳過私有化構造,我直接進行開闢新空間的數據深拷貝:原型模式

  以上兩種方法均可以無視單例模式,獲取多個實例化對象。

 

13,參考連接

連接1:https://blog.csdn.net/jason0539/article/details/23297037

連接2:https://blog.csdn.net/qq_35860138/article/details/86477538

相關文章
相關標籤/搜索