摘要:懶漢非線程安全,需要用一定的風騷操作控制,裝逼失敗有可能導致看一周的海綿寶寶餓漢天生線程安全,的時候就已經實例化好,該操作過于風騷會造成資源浪費單例注冊表初始化的時候,默認單例用的就是該方式特點私有構造方法,只能有一個實例。
概述單例設計模式(Singleton Pattern)是最簡單且常見的設計模式之一,主要作用是提供一個全局訪問且只實例化一次的對象,避免多實例對象的情況下引起邏輯性錯誤(實例化數量可控)...
Java中,單例模式主要分三種:懶漢式單例、餓漢式單例、登記式單例三種。
懶漢:非線程安全,需要用一定的風騷操作控制,裝逼失敗有可能導致看一周的海綿寶寶
餓漢:天生線程安全,ClassLoad的時候就已經實例化好,該操作過于風騷會造成資源浪費
單例注冊表:Spring初始化Bean的時候,默認單例用的就是該方式
特點
私有構造方法,只能有一個實例。
私有靜態引用指向自己實例,必須是自己在內部創建的唯一實例。
單例類給其它對象提供的都是自己創建的唯一實例
案例
在計算機系統中,內存、線程、CPU等使用情況都可以再任務管理器中看到,但始終只能打開一個任務管理器,它在Windows操作系統中是具備唯一性的,因為彈多個框多次采集數據浪費性能不說,采集數據存在誤差那就有點逗比了不是么...
每臺電腦只有一個打印機后臺處理程序
線程池的設計一般也是采用單例模式,方便對池中的線程進行控制
注意事項
實現方式種類較多,有的非線程安全方式的創建需要特別注意,且在使用的時候盡量根據場景選取較優的,線程安全了還需要去考慮性能問題。
不適用于變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。
沒有抽象層,擴展有困難。
職責過重,在一定程度上違背了單一職責原則。
使用時不能用反射模式創建單例,否則會實例化一個新的對象
解鎖姿勢第一種:單一檢查(懶漢)非線程安全
public class LazyLoadBalancer { private static LazyLoadBalancer loadBalancer; private Listservers = 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 == null if (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.正好被其它線程觀察到對象非空直接返回使用
mem = allocate(); //LazyLoadBalancer 分配內存 instance = mem; //注意當前實例已經不為空了 initByLoadBalancer(instance); //但是還有其它實例未初始化
存在問題: 首先我們一定要清楚,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,因此既可以實現延遲加載,又可以保證線程安全,不影響系統性能(居家旅行必備良藥?。?/p>
第五種:枚舉特性(懶漢)線程安全
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(歡迎調戲)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67876.html
摘要:但是的語義不足以確保遞增操作的原子性,在多線程的情況下,線程不一定是安全的。檢查某個狀態標記,以判斷是否退出循環某個方法這邊和用普通的變量的區別是,在多線程的情況下,取到后,的值被改變了,判斷會不正確。 多線程為什么是不安全的 這邊簡單的講述一下,參考java并發編程學習之synchronize(一) 當線程A和線程B同時進入num = num + value; 線程A會把num的值...
摘要:享元模式屬于結構型模式的一種,又稱輕量級模式,通過共享技術有效地實現了大量細粒度對象的復用概述兩種結構狀態內部狀態享元對象內部不隨外界環境改變而改變的共享部分。 享元模式(Flyweight Pattern)屬于結構型模式的一種,又稱輕量級模式,通過共享技術有效地實現了大量細粒度對象的復用... 概述 兩種結構狀態 內部狀態:享元對象內部不隨外界環境改變而改變的共享部分。 外部狀態...
摘要:在工廠方法模式中,我們會遇到一個問題,當產品非常多時,繼續使用工廠方法模式會產生非常多的工廠類。從簡單工廠模式到抽象工廠模式,我們都是在用后一種模式解決前一種模式的缺陷,都是在最大程度降低代碼的耦合性。 單例模式 所謂單例模式,也就是說不管什么時候我們要確保只有一個對象實例存在。很多情況下,整個系統中只需要存在一個對象,所有的信息都從這個對象獲取,比如系統的配置對象,或者是線程池。這些...
閱讀 2007·2021-09-22 16:05
閱讀 9314·2021-09-22 15:03
閱讀 2885·2019-08-30 15:53
閱讀 1702·2019-08-29 11:15
閱讀 911·2019-08-26 13:52
閱讀 2353·2019-08-26 11:32
閱讀 1806·2019-08-26 10:38
閱讀 2567·2019-08-23 17:19