5、【设计模式】单列模式
在Java開發(fā)過程中,很多場景下都會碰到或要用到單例模式,在設(shè)計模式里也是經(jīng)常作為指導(dǎo)學(xué)習(xí)的熱門模式之一,相信每位開發(fā)同事都用到過。我們總是沿著前輩的足跡去做設(shè)定好的思路,往往沒去探究為何這么做,所以這篇文章對單例模式做了詳解。
一、單例模式定義:
單例模式確保某個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。在計算機系統(tǒng)中,線程池、緩存、日志對象、對話框、打印機、顯卡的驅(qū)動程序?qū)ο蟪1辉O(shè)計成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺計算機可以有若干個打印機,但只能有一個Printer Spooler,以避免兩個打印作業(yè)同時輸出到打印機中。每臺計算機可以有若干通信端口,系統(tǒng)應(yīng)當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用。總之,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。
二、單例模式特點:
  1、單例類只能有一個實例。
  2、單例類必須自己創(chuàng)建自己的唯一實例。
  3、單例類必須給所有其他對象提供這一實例。
單例模式保證了全局對象的唯一性,比如系統(tǒng)啟動讀取配置文件就需要單例保證配置的一致性。
三、線程安全的問題
一方面在獲取單例的時候,要保證不能產(chǎn)生多個實例對象,后面會詳細講到五種實現(xiàn)方式;
另一方面,在使用單例對象的時候,要注意單例對象內(nèi)的實例變量是會被多線程共享的,推薦使用無狀態(tài)的對象,不會因為多個線程的交替調(diào)度而破壞自身狀態(tài)導(dǎo)致線程安全問題,比如我們常用的VO,DTO等(局部變量是在用戶棧中的,而且用戶棧本身就是線程私有的內(nèi)存區(qū)域,所以不存在線程安全問題)。
四、單例模式的選擇
還記得我們最早使用的MVC框架Struts1中的action就是單例模式的,而到了Struts2就使用了多例。在Struts1里,當有多個請求訪問,每個都會分配一個新線程,在這些線程,操作的都是同一個action對象,每個用戶的數(shù)據(jù)都是不同的,而action卻只有一個。到了Struts2,?action對象為每一個請求產(chǎn)生一個實例,并不會帶來線程安全問題(實際上servlet容器給每個請求產(chǎn)生許多可丟棄的對象,但是并沒有影響到性能和垃圾回收問題,有時間會做下研究)。
五、實現(xiàn)單例模式的方式
1.餓漢式單例(立即加載方式)
// 餓漢式單例 public class Singleton1 {// 私有構(gòu)造private Singleton1() {}private static Singleton1 single = new Singleton1();// 靜態(tài)工廠方法public static Singleton1 getInstance() {return single;} }?
餓漢式單例在類加載初始化時就創(chuàng)建好一個靜態(tài)的對象供外部使用,除非系統(tǒng)重啟,這個對象不會改變,所以本身就是線程安全的。
Singleton通過將構(gòu)造方法限定為private避免了類在外部被實例化,在同一個虛擬機范圍內(nèi),Singleton的唯一實例只能通過getInstance()方法訪問。(事實上,通過Java反射機制是能夠?qū)嵗瘶?gòu)造方法為private的類的,那基本上會使所有的Java單例實現(xiàn)失效。此問題在此處不做討論,姑且閉著眼就認為反射機制不存在。)
實際應(yīng)用:
//HystrixCommandFactory.classprivate static final HystrixCommandFactory INSTANCE = new HystrixCommandFactory();private HystrixCommandFactory() {}public static HystrixCommandFactory getInstance() {return INSTANCE;}?
2.懶漢式單例(延遲加載方式)
// 懶漢式單例 public class Singleton2 {// 私有構(gòu)造private Singleton2() {}
private static Singleton2 single = null;
public static Singleton2 getInstance() {if(single == null){single = new Singleton2();}return single;} }
?
該示例雖然用延遲加載方式實現(xiàn)了懶漢式單例,但在多線程環(huán)境下會產(chǎn)生多個single對象,如何改造請看以下方式:
使用synchronized同步鎖
public class Singleton3 {// 私有構(gòu)造private Singleton3() {}private static Singleton3 single = null;public static Singleton3 getInstance() {// 等同于 synchronized public static Singleton3 getInstance()synchronized(Singleton3.class){// 注意:里面的判斷是一定要加的,否則出現(xiàn)線程安全問題if(single == null){single = new Singleton3();}}return single;} }在方法上加synchronized同步鎖或是用同步代碼塊對類加同步鎖,此種方式雖然解決了多個實例對象問題,但是該方式運行效率卻很低下,下一個線程想要獲取對象,就必須等待上一個線程釋放鎖之后,才可以繼續(xù)運行。
public class Singleton4 {// 私有構(gòu)造private Singleton4() {}private static Singleton4 single = null;// 雙重檢查public static Singleton4 getInstance() {if (single == null) {synchronized (Singleton4.class) {if (single == null) {single = new Singleton4();}}}return single;} }?
使用雙重檢查進一步做了優(yōu)化,可以避免整個方法被鎖,只對需要鎖的代碼部分加鎖,可以提高執(zhí)行效率。
3.靜態(tài)內(nèi)部類實現(xiàn)
public class Singleton6 {// 私有構(gòu)造private Singleton6() {}// 靜態(tài)內(nèi)部類private static class InnerObject{private static Singleton6 single = new Singleton6();}public static Singleton6 getInstance() {return InnerObject.single;} }?
靜態(tài)內(nèi)部類雖然保證了單例在多線程并發(fā)下的線程安全性,但是在遇到序列化對象時,默認的方式運行得到的結(jié)果就是多例的。這種情況不多做說明了,使用時請注意。
4.static靜態(tài)代碼塊實現(xiàn)
public class Singleton6 {// 私有構(gòu)造private Singleton6() {}private static Singleton6 single = null;// 靜態(tài)代碼塊static{single = new Singleton6();}public static Singleton6 getInstance() {return single;} }?
5.內(nèi)部枚舉類實現(xiàn)
public class SingletonFactory {// 內(nèi)部枚舉類private enum EnmuSingleton{Singleton;private Singleton8 singleton;//枚舉類的構(gòu)造方法在類加載是被實例化 private EnmuSingleton(){singleton = new Singleton8();}public Singleton8 getInstance(){return singleton;}}public static Singleton8 getInstance() {return EnmuSingleton.Singleton.getInstance();} }class Singleton8{public Singleton8(){} }?
場景:Spring注冊單列Bean
Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64) //spring注冊bean存放位置//DefaultSingletonBeanRegistry.class@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;}
轉(zhuǎn)載于:https://www.cnblogs.com/itplay/p/9981700.html
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的5、【设计模式】单列模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 1-5三整数排序
- 下一篇: 面试重点:设计模式(二)——创建模式
