摘要:缺點(diǎn)每次調(diào)用都有線程開銷延遲初始化單例默認(rèn)構(gòu)造方法為,避免用戶用構(gòu)造出新對(duì)象獲取單例的靜態(tài)工廠同步方法延遲初始化單例使用同步方法保證多線程操作只實(shí)例化一個(gè)實(shí)力單例模式。
主要分為兩種:
直接初始化
延遲初始化
直接初始化 直接初始化final靜態(tài)成員線程安全:JVM保證final靜態(tài)成員只會(huì)被初始化一次
公有靜態(tài)成員是個(gè)final域,直接引用成員獲取單例/** * 公有靜態(tài)成員是個(gè)final域 * 直接引用成員獲取單例 */ public class Singleton1 { public static final Singleton1 INSTANCE = new Singleton1(); /** * 默認(rèn)構(gòu)造方法為private,避免用戶用new構(gòu)造出新對(duì)象 */ private Singleton1() {} public void someMethod() {} public static void main(String[] args) { Singleton1.INSTANCE.someMethod(); } }公有的成員是個(gè)靜態(tài)工廠方法,通過該方法獲取單例。
提供了靈活性,在不改變API的前提下,可以改變?cè)擃愂欠駪?yīng)該為單例的想法。
比如改成為每個(gè)調(diào)用該方法的線程返回一個(gè)唯一的實(shí)例(ThreadLocal
/** * 公有的成員是個(gè)靜態(tài)工廠方法,通過該方法獲取單例 */ public class Singleton2 { private static final Singleton2 INSTANCE = new Singleton2(); /** * 默認(rèn)構(gòu)造方法為private,避免用戶用new構(gòu)造出新對(duì)象 */ private Singleton2() {} /** * 獲取單例的靜態(tài)工廠方法 * @return Singleton2 單例 */ public static Singleton2 getInstance() { return INSTANCE; } public void someMethod() {} public static void main(String[] args) { Singleton2.getInstance().someMethod(); } }包含單個(gè)元素的枚舉類型(enum)
由枚舉類型的性質(zhì)保證枚舉常量INSTANCE是唯一實(shí)例
/** * 一個(gè)包含單個(gè)元素的枚舉類型 * 枚舉類型保證每個(gè)枚舉常量都是一個(gè)單例 */ public enum EnumSingleton { INSTANCE; public void someMethod() { /** .... */} public static void main(String[] args) { EnumSingleton.INSTANCE.someMethod(); } }延遲初始化 直接在靜態(tài)工廠方法上加 synchronized。缺點(diǎn):每次調(diào)用都有線程開銷
/** * 延遲初始化單例 */ public class LazyInitSingleton1 { private static LazyInitSingleton1 INSTANCE; /** * 默認(rèn)構(gòu)造方法為private,避免用戶用new構(gòu)造出新對(duì)象 */ private LazyInitSingleton1() {} /** * 獲取單例的靜態(tài)工廠同步方法 * 延遲初始化單例 * 使用同步方法保證多線程操作只實(shí)例化一個(gè)實(shí)力 * @return LazyInitSingleton1 單例 */ public synchronized static LazyInitSingleton1 getInstance() { if (INSTANCE == null) { INSTANCE = new LazyInitSingleton1(); } return INSTANCE; } public void someMethod() {} public static void main(String[] args) { Singleton2.getInstance().someMethod(); } }lazy initialization holder class 模式。(參考《Effective Java》第71條:慎用延遲初始化
優(yōu)點(diǎn):避免同步方法的開銷。
getInstance第一次被調(diào)用時(shí),讀取SingletonHolder.field,導(dǎo)致SingletonHolder類得到初始化
/** * lazy initialization holder class 模式 * 避免同步方法的開銷 */ public class LazyInitSingleton2 { private static class SingletonHolder { static final LazyInitSingleton2 field = computeFieldValue(); private static LazyInitSingleton2 computeFieldValue() { return new LazyInitSingleton2(); } } private LazyInitSingleton2() {} public static LazyInitSingleton2 getInstance() { return SingletonHolder.field; } }雙重檢測(cè),降低同步方法開銷。(參考《Effective Java》第71條:慎用延遲初始化)
INSTANCE 使用 volatile 修飾符:防止JVM的即時(shí)編譯器對(duì)INSTANCE = new LazyInitSingleton3()操作進(jìn)行指令重排序。
/** * 延遲初始化,雙重檢測(cè),降低同步方法開銷 */ public class LazyInitSingleton3 { /** * 注意:使用了 volatile 修飾符 */ private static volatile LazyInitSingleton3 INSTANCE; private LazyInitSingleton3() {} public static LazyInitSingleton3 getInstance() { // 第一次判斷無(wú)需同步,如果 INSTANCE 已經(jīng)被初始化, // 就直接返回,沒有同步開銷 if (INSTANCE == null) { // 如果判斷為空(多線程并發(fā)執(zhí)行 getInstance,導(dǎo)致很多線程判斷外層INSTANCE == NULL) synchronized (LazyInitSingleton3.class) { // 進(jìn)入同步后再判斷一次, // 保證只有一個(gè)線程賦值給 INSTANCE, // 后續(xù)進(jìn)來(lái)執(zhí)行的線程都會(huì)判斷 INSTANCE != NULL,不會(huì)再賦值 if (INSTANCE == null) { INSTANCE = new LazyInitSingleton3(); } } } return INSTANCE; } }為什么INSTANCE要使用volatile修飾符
在JVM中,new操作做了下面3件事:
給要new的對(duì)象LazyInitSingleton3分配內(nèi)存空間
調(diào)用LazyInitSingleton3的構(gòu)造函數(shù)來(lái)初始化對(duì)象
將INSTANCE指向步驟1中分配的內(nèi)存空間
由于JVM存在指令重排序的優(yōu)化,上面第2步和第3步順序是無(wú)法保證的(1-2-3或者1-3-2)。
如果執(zhí)行步驟是1-3-2,那么假設(shè)線程A執(zhí)行到第3步,但第2步還未執(zhí)行,此時(shí)線程B調(diào)用getInstance()發(fā)現(xiàn)INSTANCE非空(但未被初始化),直接返回INSTANCE,之后線程B對(duì)INSTANCE操作可能會(huì)發(fā)生錯(cuò)誤(由于對(duì)象還未被初始化)。
volatile修飾符防止指令重排序的優(yōu)化,保證執(zhí)行順序是1-2-3。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69447.html
摘要:總結(jié)我們主要介紹到了以下幾種方式實(shí)現(xiàn)單例模式餓漢方式線程安全懶漢式非線程安全和關(guān)鍵字線程安全版本懶漢式雙重檢查加鎖版本枚舉方式參考設(shè)計(jì)模式中文版第二版設(shè)計(jì)模式深入理解單例模式我是一個(gè)以架構(gòu)師為年之內(nèi)目標(biāo)的小小白。 初遇設(shè)計(jì)模式在上個(gè)寒假,當(dāng)時(shí)把每個(gè)設(shè)計(jì)模式過了一遍,對(duì)設(shè)計(jì)模式有了一個(gè)最初級(jí)的了解。這個(gè)學(xué)期借了幾本設(shè)計(jì)模式的書籍看,聽了老師的設(shè)計(jì)模式課,對(duì)設(shè)計(jì)模式算是有個(gè)更進(jìn)一步的認(rèn)識(shí)。...
摘要:在寫單例模式的代碼之前,我們先簡(jiǎn)單了解一下兩個(gè)知識(shí)點(diǎn),關(guān)于類的加載順序和關(guān)鍵字。懶漢和餓漢在程序編寫上,一般將單例模式分為兩種,分別是餓漢式和懶漢式,餓漢式在類加載時(shí)就完成了初始化,所以類加載比較慢,但獲取對(duì)象的速度快。 定義 單例模式是比較常見的一種設(shè)計(jì)模式,目的是保證一個(gè)類只能有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,避免頻繁創(chuàng)建對(duì)象,節(jié)約內(nèi)存。 單例模式的應(yīng)用場(chǎng)景很多, 比如...
摘要:下面我們來(lái)看看看中的單例模式,中使用的是單例注冊(cè)表的特殊方式實(shí)現(xiàn)的單例模式,所以說模式是死的,需要靈活得運(yùn)用。 本文循序漸進(jìn)介紹單例模式的幾種實(shí)現(xiàn)方式,以及Jdk中使用到單例模式的例子,以及sring框架中使用到的單例模式例子。 餓漢式 package signgleton; /** * 單例模式簡(jiǎn)單的實(shí)現(xiàn) */ public class Singleton { priv...
摘要:?jiǎn)卫J降膸追N實(shí)現(xiàn)方法具體如下懶漢模式優(yōu)點(diǎn)可以延遲加載缺點(diǎn)多線程不安全餓漢模式優(yōu)點(diǎn)多線程安全缺點(diǎn)加載類時(shí)就初始化完成無(wú)法延時(shí)加載雙重檢查優(yōu)點(diǎn)多線程安全延遲加載缺點(diǎn)同步耗時(shí)靜態(tài)內(nèi)部類優(yōu)點(diǎn)多線程安全延遲加載耗時(shí)短與雙重檢查相比用緩存實(shí)現(xiàn)優(yōu) showImg(http://7xjhi6.com1.z0.glb.clouddn.com/Java-Design-Patterns-Logo.png)...
摘要:?jiǎn)卫J揭c(diǎn)私有的構(gòu)造方法指向?qū)嵗乃接徐o態(tài)引用獲取實(shí)例對(duì)象的公有靜態(tài)方法餓漢模式非線程安全私有化構(gòu)造函數(shù)懶漢模式非線程安全私有化構(gòu)造函數(shù)雙重線程鎖檢查單例線程安全私有化構(gòu)造函數(shù)枚舉單例更多關(guān)于的文章請(qǐng)戳這里您的留言意見是對(duì)我最大的支持我的 單例模式要點(diǎn) 私有的構(gòu)造方法 指向?qū)嵗乃接徐o態(tài)引用 獲取實(shí)例對(duì)象的公有靜態(tài)方法 餓漢模式(非線程安全) public class Singl...
閱讀 3020·2021-10-12 10:12
閱讀 3073·2021-09-22 16:04
閱讀 3306·2019-08-30 15:54
閱讀 2616·2019-08-29 16:59
閱讀 2929·2019-08-29 16:08
閱讀 880·2019-08-29 11:20
閱讀 3503·2019-08-28 18:08
閱讀 661·2019-08-26 13:43