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