摘要:懶漢非線程安全,需要用一定的風(fēng)騷操作控制,裝逼失敗有可能導(dǎo)致看一周的海綿寶寶餓漢天生線程安全,的時(shí)候就已經(jīng)實(shí)例化好,該操作過(guò)于風(fēng)騷會(huì)造成資源浪費(fèi)單例注冊(cè)表初始化的時(shí)候,默認(rèn)單例用的就是該方式特點(diǎn)私有構(gòu)造方法,只能有一個(gè)實(shí)例。
概述單例設(shè)計(jì)模式(Singleton Pattern)是最簡(jiǎn)單且常見(jiàn)的設(shè)計(jì)模式之一,主要作用是提供一個(gè)全局訪問(wèn)且只實(shí)例化一次的對(duì)象,避免多實(shí)例對(duì)象的情況下引起邏輯性錯(cuò)誤(實(shí)例化數(shù)量可控)...
Java中,單例模式主要分三種:懶漢式單例、餓漢式單例、登記式單例三種。
懶漢:非線程安全,需要用一定的風(fēng)騷操作控制,裝逼失敗有可能導(dǎo)致看一周的海綿寶寶
餓漢:天生線程安全,ClassLoad的時(shí)候就已經(jīng)實(shí)例化好,該操作過(guò)于風(fēng)騷會(huì)造成資源浪費(fèi)
單例注冊(cè)表:Spring初始化Bean的時(shí)候,默認(rèn)單例用的就是該方式
特點(diǎn)
私有構(gòu)造方法,只能有一個(gè)實(shí)例。
私有靜態(tài)引用指向自己實(shí)例,必須是自己在內(nèi)部創(chuàng)建的唯一實(shí)例。
單例類(lèi)給其它對(duì)象提供的都是自己創(chuàng)建的唯一實(shí)例
案例
在計(jì)算機(jī)系統(tǒng)中,內(nèi)存、線程、CPU等使用情況都可以再任務(wù)管理器中看到,但始終只能打開(kāi)一個(gè)任務(wù)管理器,它在Windows操作系統(tǒng)中是具備唯一性的,因?yàn)閺椂鄠€(gè)框多次采集數(shù)據(jù)浪費(fèi)性能不說(shuō),采集數(shù)據(jù)存在誤差那就有點(diǎn)逗比了不是么...
每臺(tái)電腦只有一個(gè)打印機(jī)后臺(tái)處理程序
線程池的設(shè)計(jì)一般也是采用單例模式,方便對(duì)池中的線程進(jìn)行控制
注意事項(xiàng)
實(shí)現(xiàn)方式種類(lèi)較多,有的非線程安全方式的創(chuàng)建需要特別注意,且在使用的時(shí)候盡量根據(jù)場(chǎng)景選取較優(yōu)的,線程安全了還需要去考慮性能問(wèn)題。
不適用于變化的對(duì)象,如果同一類(lèi)型的對(duì)象總是要在不同的用例場(chǎng)景發(fā)生變化,單例就會(huì)引起數(shù)據(jù)的錯(cuò)誤,不能保存彼此的狀態(tài)。
沒(méi)有抽象層,擴(kuò)展有困難。
職責(zé)過(guò)重,在一定程度上違背了單一職責(zé)原則。
使用時(shí)不能用反射模式創(chuàng)建單例,否則會(huì)實(shí)例化一個(gè)新的對(duì)象
解鎖姿勢(shì)第一種:?jiǎn)我粰z查(懶漢)非線程安全
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() { // 第一步:假設(shè)T1,T2兩個(gè)線程同時(shí)進(jìn)來(lái)且滿(mǎn)足 loadBalancer == null if (loadBalancer == null) { // 第二步:那么 loadBalancer 即會(huì)被實(shí)例化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("轉(zhuǎn)發(fā)至:" + balancer1.getServer())); } }
日志
hashCode:460141958 hashCode:460141958 轉(zhuǎn)發(fā)至:Server 2 轉(zhuǎn)發(fā)至:Server 2 轉(zhuǎn)發(fā)至:Server 2 轉(zhuǎn)發(fā)至:Server 1 轉(zhuǎn)發(fā)至:Server 2
分析: 在單線程環(huán)境一切正常,balancer1和balancer2兩個(gè)對(duì)象的hashCode一模一樣,由此可以判斷出堆棧中只有一份內(nèi)容,不過(guò)該代碼塊中存在線程安全隱患,因?yàn)槿狈Ω?jìng)爭(zhēng)條件,多線程環(huán)境資源競(jìng)爭(zhēng)的時(shí)候就顯得不太樂(lè)觀了,請(qǐng)看上文代碼注釋內(nèi)容
第二種:無(wú)腦上鎖(懶漢)線程安全,性能較差,第一種升級(jí)版
public synchronized static LazyLoadBalancer getInstance() { if (loadBalancer == null) { loadBalancer = new LazyLoadBalancer(); } return loadBalancer; }
分析: 毫無(wú)疑問(wèn),知道synchronized關(guān)鍵字的都知道,同步方法在鎖沒(méi)釋放之前,其它線程都在排隊(duì)候著呢,想不安全都不行啊,但在安全的同時(shí),性能方面就顯得短板了,我就初始化一次,你丫的每次來(lái)都上個(gè)鎖,不累的嗎(沒(méi)關(guān)系,它是為了第三種做鋪墊的)..
第三種:雙重檢查鎖(DCL),完全就是前兩種的結(jié)合體啊,有木有,只是將同步方法升級(jí)成了同步代碼塊
//劃重點(diǎn)了 **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.假設(shè)new LazyLoadBalancer()加載內(nèi)容過(guò)多
2.因重排而導(dǎo)致loadBalancer提前不為空
3.正好被其它線程觀察到對(duì)象非空直接返回使用
mem = allocate(); //LazyLoadBalancer 分配內(nèi)存 instance = mem; //注意當(dāng)前實(shí)例已經(jīng)不為空了 initByLoadBalancer(instance); //但是還有其它實(shí)例未初始化
存在問(wèn)題: 首先我們一定要清楚,DCL是不能保證線程安全的,稍微了解過(guò)JVM的就清楚,對(duì)比C/C++它始終缺少一個(gè)正式的內(nèi)存模型,所以為了提升性能,它還會(huì)做一次指令重排操作,這個(gè)時(shí)候就會(huì)導(dǎo)致loadBalancer提前不為空,正好被其它線程觀察到對(duì)象非空直接返回使用(但實(shí)際還有部分內(nèi)容沒(méi)加載完成)
解決方案: 用volatile修飾loadBalancer,因?yàn)?b>volatile修飾的成員變量可以確保多個(gè)線程都能夠順序處理,它會(huì)屏蔽JVM指令重排帶來(lái)的性能優(yōu)化。
volatile詳解:http://blog.battcn.com/2017/10/18/java/thread/thread-volatile/
第四種:Demand Holder (懶漢)線程安全,推薦使用
private LazyLoadBalancer() {} private static class LoadBalancerHolder { //在JVM中 final 對(duì)象只會(huì)被實(shí)例化一次,無(wú)法修改 private final static LazyLoadBalancer INSTANCE = new LazyLoadBalancer(); } public static LazyLoadBalancer getInstance() { return LoadBalancerHolder.INSTANCE; }
分析: 在Demand Holder中,我們?cè)?b>LazyLoadBalancer里增加一個(gè)靜態(tài)(static)內(nèi)部類(lèi),在該內(nèi)部類(lèi)中創(chuàng)建單例對(duì)象,再將
該單例對(duì)象通過(guò)getInstance()方法返回給外部使用,由于靜態(tài)單例對(duì)象沒(méi)有作為LazyLoadBalancer的成員變量直接實(shí)例化,類(lèi)加載時(shí)并不會(huì)實(shí)例化LoadBalancerHolder,因此既可以實(shí)現(xiàn)延遲加載,又可以保證線程安全,不影響系統(tǒng)性能(居家旅行必備良藥?。?/p>
第五種:枚舉特性(懶漢)線程安全
enum Lazy { INSTANCE; private LazyLoadBalancer loadBalancer; //枚舉的特性,在JVM中只會(huì)被實(shí)例化一次 Lazy() { loadBalancer = new LazyLoadBalancer(); } public LazyLoadBalancer getInstance() { return loadBalancer; } }
分析: 相比上一種,該方式同樣是用到了JAVA特性:枚舉類(lèi)保證只有一個(gè)實(shí)例(即使使用反射機(jī)制也無(wú)法多次實(shí)例化一個(gè)枚舉量)
第六種:餓漢單例(天生線程安全),
public class EagerLoadBalancer { private final static EagerLoadBalancer INSTANCE = new EagerLoadBalancer(); private EagerLoadBalancer() {} public static EagerLoadBalancer getInstance() { return INSTANCE; } }
分析: 利用ClassLoad機(jī)制,在加載時(shí)進(jìn)行實(shí)例化,同時(shí)靜態(tài)方法只在編譯期間執(zhí)行一次初始化,也就只有一個(gè)對(duì)象。使用的時(shí)候已被初始化完畢可以直接調(diào)用,但是相比懶漢模式,它在使用的時(shí)候速度最快,但這玩意就像自己挖的坑哭著也得跳,你不用也得初始化一份在內(nèi)存中占個(gè)坑...
- 說(shuō)點(diǎn)什么全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter2/battcn-singleton
個(gè)人QQ:1837307557
battcn開(kāi)源群(適合新手):391619659
微信公眾號(hào):battcn(歡迎調(diào)戲)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/67876.html
摘要:但是的語(yǔ)義不足以確保遞增操作的原子性,在多線程的情況下,線程不一定是安全的。檢查某個(gè)狀態(tài)標(biāo)記,以判斷是否退出循環(huán)某個(gè)方法這邊和用普通的變量的區(qū)別是,在多線程的情況下,取到后,的值被改變了,判斷會(huì)不正確。 多線程為什么是不安全的 這邊簡(jiǎn)單的講述一下,參考java并發(fā)編程學(xué)習(xí)之synchronize(一) 當(dāng)線程A和線程B同時(shí)進(jìn)入num = num + value; 線程A會(huì)把num的值...
摘要:享元模式屬于結(jié)構(gòu)型模式的一種,又稱(chēng)輕量級(jí)模式,通過(guò)共享技術(shù)有效地實(shí)現(xiàn)了大量細(xì)粒度對(duì)象的復(fù)用概述兩種結(jié)構(gòu)狀態(tài)內(nèi)部狀態(tài)享元對(duì)象內(nèi)部不隨外界環(huán)境改變而改變的共享部分。 享元模式(Flyweight Pattern)屬于結(jié)構(gòu)型模式的一種,又稱(chēng)輕量級(jí)模式,通過(guò)共享技術(shù)有效地實(shí)現(xiàn)了大量細(xì)粒度對(duì)象的復(fù)用... 概述 兩種結(jié)構(gòu)狀態(tài) 內(nèi)部狀態(tài):享元對(duì)象內(nèi)部不隨外界環(huán)境改變而改變的共享部分。 外部狀態(tài)...
摘要:在工廠方法模式中,我們會(huì)遇到一個(gè)問(wèn)題,當(dāng)產(chǎn)品非常多時(shí),繼續(xù)使用工廠方法模式會(huì)產(chǎn)生非常多的工廠類(lèi)。從簡(jiǎn)單工廠模式到抽象工廠模式,我們都是在用后一種模式解決前一種模式的缺陷,都是在最大程度降低代碼的耦合性。 單例模式 所謂單例模式,也就是說(shuō)不管什么時(shí)候我們要確保只有一個(gè)對(duì)象實(shí)例存在。很多情況下,整個(gè)系統(tǒng)中只需要存在一個(gè)對(duì)象,所有的信息都從這個(gè)對(duì)象獲取,比如系統(tǒng)的配置對(duì)象,或者是線程池。這些...
閱讀 2181·2021-09-22 16:05
閱讀 9518·2021-09-22 15:03
閱讀 2943·2019-08-30 15:53
閱讀 1761·2019-08-29 11:15
閱讀 972·2019-08-26 13:52
閱讀 2430·2019-08-26 11:32
閱讀 1870·2019-08-26 10:38
閱讀 2637·2019-08-23 17:19