一起学设计模式 - 单例模式
單例設計模式(Singleton Pattern)是最簡單且常見的設計模式之一,主要作用是提供一個全局訪問且只實例化一次的對象,避免多實例對象的情況下引起邏輯性錯誤(實例化數量可控)...
<!-- more -->
概述
Java中,單例模式主要分三種:懶漢式單例、餓漢式單例、登記式單例三種。
- 懶漢:非線程安全,需要用一定的風騷操作控制,裝逼失敗有可能導致看一周的海綿寶寶
- 餓漢:天生線程安全,ClassLoad的時候就已經實例化好,該操作過于風騷會造成資源浪費
- 單例注冊表:Spring初始化Bean的時候,默認單例用的就是該方式
特點
- 私有構造方法,只能有一個實例。
- 私有靜態引用指向自己實例,必須是自己在內部創建的唯一實例。
- 單例類給其它對象提供的都是自己創建的唯一實例
案例
- 在計算機系統中,內存、線程、CPU等使用情況都可以再任務管理器中看到,但始終只能打開一個任務管理器,它在Windows操作系統中是具備唯一性的,因為彈多個框多次采集數據浪費性能不說,采集數據存在誤差那就有點逗比了不是么...
- 每臺電腦只有一個打印機后臺處理程序
- 線程池的設計一般也是采用單例模式,方便對池中的線程進行控制
注意事項
- 實現方式種類較多,有的非線程安全方式的創建需要特別注意,且在使用的時候盡量根據場景選取較優的,線程安全了還需要去考慮性能問題。
- 不適用于變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。
- 沒有抽象層,擴展有困難。
- 職責過重,在一定程度上違背了單一職責原則。
- 使用時不能用反射模式創建單例,否則會實例化一個新的對象
解鎖姿勢
第一種:單一檢查(懶漢)非線程安全
public class LazyLoadBalancer {private static LazyLoadBalancer loadBalancer;private List<String> servers = null;private LazyLoadBalancer() {servers = new ArrayList<>();}public void addServer(String server) {servers.add(server);}public String getServer() {Random random = new Random();int i = random.nextInt(servers.size());return servers.get(i);}public static LazyLoadBalancer getInstance() {// 第一步:假設T1,T2兩個線程同時進來且滿足 loadBalancer == nullif (loadBalancer == null) {// 第二步:那么 loadBalancer 即會被實例化2次loadBalancer = new LazyLoadBalancer();}return loadBalancer;}public static void main(String[] args) {LazyLoadBalancer balancer1 = LazyLoadBalancer.getInstance();LazyLoadBalancer balancer2 = LazyLoadBalancer.getInstance();System.out.println("hashCode:"+balancer1.hashCode());System.out.println("hashCode:"+balancer2.hashCode());balancer1.addServer("Server 1");balancer2.addServer("Server 2");IntStream.range(0, 5).forEach(i -> System.out.println("轉發至:" + balancer1.getServer()));} }日志
hashCode:460141958 hashCode:460141958 轉發至:Server 2 轉發至:Server 2 轉發至:Server 2 轉發至:Server 1 轉發至:Server 2分析: 在單線程環境一切正常,balancer1和balancer2兩個對象的hashCode一模一樣,由此可以判斷出堆棧中只有一份內容,不過該代碼塊中存在線程安全隱患,因為缺乏競爭條件,多線程環境資源競爭的時候就顯得不太樂觀了,請看上文代碼注釋內容
第二種:無腦上鎖(懶漢)線程安全,性能較差,第一種升級版
public synchronized static LazyLoadBalancer getInstance() {if (loadBalancer == null) {loadBalancer = new LazyLoadBalancer();}return loadBalancer; }分析: 毫無疑問,知道synchronized關鍵字的都知道,同步方法在鎖沒釋放之前,其它線程都在排隊候著呢,想不安全都不行啊,但在安全的同時,性能方面就顯得短板了,我就初始化一次,你丫的每次來都上個鎖,不累的嗎(沒關系,它是為了第三種做鋪墊的)..
第三種:雙重檢查鎖(DCL),完全就是前兩種的結合體啊,有木有,只是將同步方法升級成了同步代碼塊
//劃重點了 **volatile** private volatile static LazyLoadBalancer loadBalancer;public static LazyLoadBalancer getInstance() {if (loadBalancer == null) {synchronized (LazyLoadBalancer.class) {if (loadBalancer == null) {loadBalancer = new LazyLoadBalancer();}}}return loadBalancer; }1.假設new LazyLoadBalancer()加載內容過多
2.因重排而導致loadBalancer提前不為空
3.正好被其它線程觀察到對象非空直接返回使用
存在問題: 首先我們一定要清楚,DCL是不能保證線程安全的,稍微了解過JVM的就清楚,對比C/C++它始終缺少一個正式的內存模型,所以為了提升性能,它還會做一次指令重排操作,這個時候就會導致loadBalancer提前不為空,正好被其它線程觀察到對象非空直接返回使用(但實際還有部分內容沒加載完成)
解決方案: 用volatile修飾loadBalancer,因為volatile修飾的成員變量可以確保多個線程都能夠順序處理,它會屏蔽JVM指令重排帶來的性能優化。
volatile詳解:http://blog.battcn.com/2017/10/18/java/thread/thread-volatile/
第四種:Demand Holder (懶漢)線程安全,推薦使用
private LazyLoadBalancer() {}private static class LoadBalancerHolder {//在JVM中 final 對象只會被實例化一次,無法修改private final static LazyLoadBalancer INSTANCE = new LazyLoadBalancer(); }public static LazyLoadBalancer getInstance() {return LoadBalancerHolder.INSTANCE; }分析: 在Demand Holder中,我們在LazyLoadBalancer里增加一個靜態(static)內部類,在該內部類中創建單例對象,再將
該單例對象通過getInstance()方法返回給外部使用,由于靜態單例對象沒有作為LazyLoadBalancer的成員變量直接實例化,類加載時并不會實例化LoadBalancerHolder,因此既可以實現延遲加載,又可以保證線程安全,不影響系統性能(居家旅行必備良藥啊)
第五種:枚舉特性(懶漢)線程安全
enum Lazy {INSTANCE;private LazyLoadBalancer loadBalancer;//枚舉的特性,在JVM中只會被實例化一次Lazy() {loadBalancer = new LazyLoadBalancer();}public LazyLoadBalancer getInstance() {return loadBalancer;} }分析: 相比上一種,該方式同樣是用到了JAVA特性:枚舉類保證只有一個實例(即使使用反射機制也無法多次實例化一個枚舉量)
第六種:餓漢單例(天生線程安全),
public class EagerLoadBalancer {private final static EagerLoadBalancer INSTANCE = new EagerLoadBalancer();private EagerLoadBalancer() {}public static EagerLoadBalancer getInstance() {return INSTANCE;} }分析: 利用ClassLoad機制,在加載時進行實例化,同時靜態方法只在編譯期間執行一次初始化,也就只有一個對象。使用的時候已被初始化完畢可以直接調用,但是相比懶漢模式,它在使用的時候速度最快,但這玩意就像自己挖的坑哭著也得跳,你不用也得初始化一份在內存中占個坑...
- 說點什么
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter2/battcn-singleton
- 個人QQ:1837307557
- battcn開源群(適合新手):391619659
微信公眾號:battcn(歡迎調戲)
總結
以上是生活随笔為你收集整理的一起学设计模式 - 单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 中SharedPrefe
- 下一篇: CSVN部署安装,实现web管理svn