Java设计模式之创建型:单例模式
一、什么是單例模式:
????????單例模式可以確保系統(tǒng)中某個類只有一個實例,該類自行實例化并向整個系統(tǒng)提供這個實例的公共訪問點(diǎn),除了該公共訪問點(diǎn),不能通過其他途徑訪問該實例。單例模式的優(yōu)點(diǎn)在于:
- 系統(tǒng)中只存在一個共用的實例對象,無需頻繁創(chuàng)建和銷毀對象,節(jié)約了系統(tǒng)資源,提高系統(tǒng)的性能
- 可以嚴(yán)格控制客戶怎么樣以及何時訪問單例對象。
單例模式有以下特點(diǎn):
- (1)單例類只能有一個實例;
- (2)單例類必須自己創(chuàng)建自己的唯一實例;
- (3)單例類必須給所有其他對象提供這一實例。
????????在計算機(jī)系統(tǒng)中,線程池、緩存、日志對象、對話框、打印機(jī)、顯卡的驅(qū)動程序?qū)ο蟪1辉O(shè)計成單例,這些應(yīng)用都或多或少具有資源管理器的功能。每臺計算機(jī)可以有若干個打印機(jī),但只能有一個Printer Spooler,以避免兩個打印作業(yè)同時輸出到打印機(jī)中。每臺計算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用??傊?#xff0c;選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。
????????單例模式的寫法有好幾種,這里主要介紹三種:懶漢式單例、餓漢式單例、登記式單例。
二、懶漢式單例:
//懶漢式單例類.在第一次調(diào)用的時候?qū)嵗约? public class Singleton {private Singleton() {}private static Singleton single=null;//靜態(tài)工廠方法 public static Singleton getInstance() {if (single == null) { single = new Singleton();} return single;} }Singleton 通過私有化構(gòu)造函數(shù),避免類在外部被實例化,而且只能通過?getInstance() 方法獲取 Singleton 的唯一實例。但是以上懶漢式單例的實現(xiàn)是線程不安全的,在并發(fā)環(huán)境下可能出現(xiàn)多個 Singleton 實例的問題。要實現(xiàn)線程安全,有以下三種方式,都是對 getInstance() 這個方法改造,保證了懶漢式單例的線程安全:
線程安全:如果程序每次運(yùn)行結(jié)果都和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。
1、在 getInstance() 方法上加同步機(jī)制:
public static synchronized Singleton getInstance() {if (single == null) { single = new Singleton();} return single; }在方法調(diào)用上加了同步,雖然線程安全了,但是每次都要同步,會影響性能,畢竟99%的情況下是不需要同步的。
2、雙重檢查鎖定:
//懶漢式單例類.在第一次調(diào)用的時候?qū)嵗约? public class Singleton {private Singleton() {}private volatile static Singleton singleton=null;public static Singleton getInstance() {if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }(1)為什么?getInstance() 方法內(nèi)需要使用兩個 if (singleton == null) 進(jìn)行判斷呢?
假設(shè)高并發(fā)下,線程A、B 都通過了第一個 if 條件。若A先搶到鎖,new 了一個對象,釋放鎖,然后線程B再搶到鎖,此時如果不做第二個 if 判斷,B線程將會再 new 一個對象。使用兩個 if 判斷,確保了只有第一次調(diào)用單例的時候才會做同步,這樣也是線程安全的,同時避免了每次都同步的性能損耗。
(2)volatile 關(guān)鍵字的作用?
volatile 的作用主要是禁止指定重排序。假設(shè)在不使用 volatile 的情況下,兩個線程A、B,都是第一次調(diào)用該單例方法,線程A先執(zhí)行 singleton?= new Singleton(),但由于構(gòu)造方法不是一個原子操作,編譯后會生成多條字節(jié)碼指令,由于 JAVA的 指令重排序,可能會先執(zhí)行 singleton?的賦值操作,該操作實際只是在內(nèi)存中開辟一片存儲對象的區(qū)域后直接返回內(nèi)存的引用,之后 singleton?便不為空了,但是實際的初始化操作卻還沒有執(zhí)行。如果此時線程B進(jìn)入,就會拿到一個不為空的但是沒有完成初始化的singleton?對象,所以需要加入volatile關(guān)鍵字,禁止指令重排序優(yōu)化,從而安全的實現(xiàn)單例。
3、靜態(tài)內(nèi)部類:
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }利用了類加載機(jī)制來保證初始化 instance 時只有一個線程,所以也是線程安全的,同時沒有性能損耗,這種比上面1、2都好一些,既實現(xiàn)了線程安全,又避免了同步帶來的性能影響。
三、餓漢式單例:
//餓漢式單例類.在類初始化時,已經(jīng)自行實例化 public class Singleton1 {private Singleton1() {}private static final Singleton1 single = new Singleton1();//靜態(tài)工廠方法 public static Singleton1 getInstance() {return single;} }餓漢式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用,以后不再改變,所以天生是線程安全的。
餓漢式和懶漢式區(qū)別:
(1)初始化時機(jī)與首次調(diào)用:
- 餓漢式是在類加載時,就將單例初始化完成,保證獲取實例的時候,單例是已經(jīng)存在的了。所以在第一次調(diào)用時速度也會更快,因為其資源已經(jīng)初始化完成。
- 懶漢式會延遲加載,只有在首次調(diào)用時才會實例化單例,如果初始化所需要的工作比較多,那么首次訪問性能上會有些延遲,不過之后就和餓漢式一樣了。
(2)線程安全方面:餓漢式天生就是線程安全的,可以直接用于多線程而不會出現(xiàn)問題,懶漢式本身是非線程安全的,需要通過額外的機(jī)制保證線程安全
四、登記式單例:
//類似Spring里面的方法,將類名注冊,下次從里面直接獲取。 public class Singleton3 {private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();static{Singleton3 single = new Singleton3();map.put(single.getClass().getName(), single);}//保護(hù)的默認(rèn)構(gòu)造子protected Singleton3(){}//靜態(tài)工廠方法,返還此類惟一的實例public static Singleton3 getInstance(String name) {if(name == null) {name = Singleton3.class.getName();System.out.println("name == null"+"--->name="+name);}if(map.get(name) == null) {try {map.put(name, (Singleton3) Class.forName(name).newInstance());} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}return map.get(name);}//一個示意性的商業(yè)方法public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) {Singleton3 single3 = Singleton3.getInstance(null);System.out.println(single3.about());} }登記式單例實際上維護(hù)了一組單例類的實例,將這些實例存放在一個Map(登記薄)中,對于已經(jīng)登記過的實例,則從Map直接返回,對于沒有登記的,則先登記,然后返回。
設(shè)計模式系列文章:
Java設(shè)計模式之創(chuàng)建型:工廠模式詳解(簡單工廠+工廠方法+抽象工廠)
Java設(shè)計模式之創(chuàng)建型:建造者模式
Java設(shè)計模式之創(chuàng)建型:單例模式
Java設(shè)計模式之創(chuàng)建型:原型模式
Java設(shè)計模式之結(jié)構(gòu)型:適配器模式
Java設(shè)計模式之結(jié)構(gòu)型:裝飾器模式
Java設(shè)計模式之結(jié)構(gòu)型:代理模式
Java設(shè)計模式之結(jié)構(gòu)型:橋接模式
Java設(shè)計模式之結(jié)構(gòu)型:外觀模式
Java設(shè)計模式之結(jié)構(gòu)型:組合模式
Java設(shè)計模式之結(jié)構(gòu)型:享元模式
Java設(shè)計模式之行為型:策略模式
Java設(shè)計模式之行為型:模板方法模式
Java設(shè)計模式之行為型:責(zé)任鏈模式
Java設(shè)計模式之行為型:觀察者模式
Java設(shè)計模式之行為型:訪問者模式
Java設(shè)計模式之行為型:中介者模式
Java設(shè)計模式之行為型:命令模式
Java設(shè)計模式之行為型:狀態(tài)模式
Java設(shè)計模式之行為型:備忘錄模式
Java設(shè)計模式之行為型:迭代器模式
Java設(shè)計模式之行為型:解釋器模式
參考文章:https://blog.csdn.net/jason0539/article/details/23297037
總結(jié)
以上是生活随笔為你收集整理的Java设计模式之创建型:单例模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java集合篇:集合细节:为集合指定初始
- 下一篇: Java设计模式之创建型:建造者模式