日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

创建型模式:单例模式(懒汉+饿汉+双锁校验+内部类+枚举)

發布時間:2025/3/21 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 创建型模式:单例模式(懒汉+饿汉+双锁校验+内部类+枚举) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

單例模式

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。

這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

注意:

·????????1、單例類只能有一個實例。

·????????2、單例類必須自己創建自己的唯一實例。

·????????3、單例類必須給所有其他對象提供這一實例。

介紹

意圖:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

主要解決:一個全局使用的類頻繁地創建與銷毀。

何時使用:當您想控制實例數目,節省系統資源的時候。

如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。

關鍵代碼:構造函數是私有的。

應用實例:?1、一個黨只能有一個主席。 2、Windows 是多進程多線程的,在操作一個文件的時候,就不可避免地出現多個進程或線程同時操作一個文件的現象,所以所有文件的處理必須通過唯一的實例來進行。 3、一些設備管理器常常設計為單例模式,比如一個電腦有兩臺打印機,在輸出的時候就要處理不能兩臺打印機打印同一個文件。

優點:?1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例(比如管理學院首頁頁面緩存)。 2、避免對資源的多重占用(比如寫文件操作)。

缺點:沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。

使用場景:?1、要求生產唯一序列號。 2、WEB 中的計數器,不用每次刷新都在數據庫里加一次,用單例先緩存起來。 3、創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。

注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成 instance 被多次實例化。

實現

我們將創建一個?SingleObject?類。SingleObject?類有它的私有構造函數和本身的一個靜態實例。

SingleObject?類提供了一個靜態方法,供外界獲取它的靜態實例。SingletonPatternDemo,我們的演示類使用?SingleObject?類來獲取?SingleObject?對象。

?

·?+ :表示public

·?- :表示private

·?#:表示protected(friendly也歸入這類)

?

步驟 1

創建一個 Singleton 類。

SingleObject.java

publicclassSingleObject{

?

?? //創建SingleObject 的一個對象

?? privatestaticSingleObject instance =newSingleObject();

?

?? //讓構造函數為 private,這樣該類就不會被實例化

?? privateSingleObject(){}

?

?? //獲取唯一可用的對象

?? public static SingleObject getInstance(){

????? return instance;

?? }

?

?? publicvoid showMessage(){

????? System.out.println("HelloWorld!");

?? }

}

步驟 2

從 singleton 類獲取唯一的對象。

SingletonPatternDemo.java

publicclassSingletonPatternDemo{

?? publicstaticvoid main(String[] args){

????? //不合法的構造函數

????? //編譯時錯誤:構造函數SingleObject() 是不可見的

????? //SingleObject object = new SingleObject();

?

????? //獲取唯一可用的對象

????? SingleObjectobject=SingleObject.getInstance();

?

????? //顯示消息

????? object.showMessage();

?? }

}

步驟 3

驗證輸出。

HelloWorld!


Java中單例(Singleton)模式是一種廣泛使用的設計模式。單例模式的主要作用是保證在Java程序中,某個類只有一個實例存在。一些管理器和控制器常被設計成單例模式。

? ?? ?單例模式有很多好處,

它能夠避免實例對象的重復創建,不僅可以減少每次創建對象的時間開銷,還可以節約內存空間;

能夠避免由于操作多個實例導致的邏輯錯誤。

如果一個對象有可能貫穿整個應用程序,而且起到了全局統一管理控制的作用,那么單例模式也許是一個值得考慮的選擇。

單例模式有很多種寫法,大部分寫法都或多或少有一些不足。下面將分別對這幾種寫法進行介紹。

1、餓漢模式

?

[java]?view plain?copy

1.? public?class?Singleton{??

2.? ????private?static?Singleton?instance?=?new?Singleton();??

3.? ????private?Singleton(){}??

4.? ????public?static?Singleton?getInstance(){??

5.? ????????return?instance;??

6.? ????}??

7.? }??

是否 Lazy 初始化:

是否多線程安全:

實現難度:

描述:這種方式比較常用,但容易產生垃圾對象。(類加載的時候就創建了對象,后面可能這個對象沒有被使用,那么就浪費了內存空間資源)
優點:沒有加鎖,執行效率會提高。
缺點:類加載時就初始化,浪費內存。


它基于 classloder 機制避免了多線程的同步問題(類加載是天然的線程安全的),不過,instance 在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法,但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

?

? ? ??從代碼中我們看到,類的構造函數定義為private的,保證其他類不能實例化此類,然后提供了一個靜態實例并返回給調用者。餓漢模式是最簡單的一種實現方式,餓漢模式在類加載的時候就對實例進行創建,實例在整個程序周期都存在。它的好處是只在類加載的時候創建一次實例,不會存在多個線程創建多個實例的情況,避免了多線程同步的問題。

它的缺點也很明顯,即使這個單例沒有用到也會被創建,而且在類加載之后就被創建,內存就被浪費了。

? ?? ?這種實現方式適合單例占用內存比較小,在初始化時就會被用到的情況。但是,如果單例占用的內存比較大,或單例只是在某個特定場景下才會用到,使用餓漢模式就不合適了,這時候就需要用到懶漢模式進行延遲加載。

2、懶漢模式

[java]?view plain?copy

1.? public?class?Singleton{??

2.? ????private?static?Singleton?instance?=?null;??

3.? ????private?Singleton(){}??

4.? ????public?static?Singleton?getInstance(){??

5.? ????????if(null?==?instance){??

6.? ????????????instance?=?new?Singleton();??

7.? ????????}??

8.? ????????return?instance;??

9.? ????}??

10. }??

是否Lazy 初始化:

是否多線程安全:(需要使用synchronized同步)

實現難度:

描述:這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。
這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。

?

?懶漢模式中單例是在需要的時候才去創建的,如果單例已經創建,再次調用獲取接口將不會重新創建新的對象,而是直接返回之前創建的對象。如果某個單例使用的次數少,并且創建單例消耗的資源較多,那么就需要實現單例的按需創建,這個時候使用懶漢模式就是一個不錯的選擇。但是這里的懶漢模式并沒有考慮線程安全問題,在多個線程可能會并發調用它的getInstance()方法,導致創建多個實例,因此需要加鎖解決線程同步問題,實現如下。

[java]?view plain?copy

1.? public?class?Singleton{??

2.? ????private?static?Singleton?instance?=?null;??

3.? ????private?Singleton(){}??

4.? ????public?static?synchronized?Singleton?getInstance(){??

5.? ????????if(null?==?instance){??

6.? ????????????instance?=?new?Singleton();??

7.? ????????}??

8.? ????????return?instance;??

9.? ????}??

10. }??

是否 Lazy 初始化:

是否多線程安全:

實現難度:

描述:這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。
優點:第一次調用才初始化,避免內存浪費。
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。

getInstance () 的性能對應用程序不是很關鍵(該方法使用不太頻繁)。

?

3、雙重校驗鎖

? ?? ?加鎖的懶漢模式看起來即解決了線程并發問題,又實現了延遲加載,然而它存在著性能問題,依然不夠完美。synchronized修飾的同步方法比一般方法要慢很多,如果多次調用getInstance(),累積的性能損耗就比較大了。因此就有了雙重校驗鎖,先看下它的實現代碼。

[java]?view plain?copy

1.? public?class?Singleton?{??

2.? ????private?static?Singleton?instance?=?null;??

3.? ????private?Singleton(){}??

4.? ????public?static?Singleton?getInstance()?{??

5.? ????????if?(instance?==?null)?{??

6.? ????????????synchronized?(Singleton.class)?{? //只有第一次時才進入同步代碼塊創建實例

7.? ????????????????if?(instance?==?null)?{//2??

8.? ????????????????????instance?=?new?Singleton();??

9.? ????????????????}??

10. ????????????}??

11. ????????}??

12. ????????return?instance;??

13. ????}??

14. }??

? ? ??可以看到上面在同步代碼塊外多了一層instance為空的判斷。由于單例對象只需要創建一次,如果后面再次調用getInstance()只需要直接返回單例對象。因此,大部分情況下,調用getInstance()都不會執行到同步代碼塊,從而提高了程序性能。不過還需要考慮一種情況,假如兩個線程A、B,A執行了if (instance == null)語句,它會認為單例對象沒有創建,此時A被掛起。CPU切到線程B也執行了同樣的語句,B也認為單例對象沒有創建,然后兩個線程依次執行同步代碼塊,并分別創建了一個單例對象為了解決這個問題,還需要在同步代碼塊中增加if(instance == null)語句,也就是上面看到的代碼2。

? ?? ?我們看到雙重校驗鎖即實現了延遲加載,又解決了線程并發問題,同時還解決了執行效率問題,是否真的就萬無一失了呢?

? ?? ?這里要提到Java中的指令重排優化(就是前面博客在講volitile關鍵字提及到的有序性)。所謂指令重排優化是指在不改變原語義的情況下,通過調整指令的執行順序讓程序運行的更快。JVM中并沒有規定編譯器優化相關的內容,也就是說JVM可以自由的進行指令重排序的優化。

? ?? ?這個問題的關鍵就在于由于指令重排優化的存在導致初始化Singleton和將對象地址賦給instance字段的順序是不確定的(即上面的第2,8行順序不確定)。

  • 為什么instance實例需要加volatile關鍵字?
  • 因為volatile的可見性?比如線程1和線程2同時進入外層check,如果沒有volatile關鍵字,因為線程的工作內存(register和CPU的L1、L2級緩存)的關系,導致不同的線程緩存了instance的副本,線程1對線程2的修改不可見?是這樣的嗎?答案:不是。因為instance是static變量,存在于方法區,對于線程而言是共享的,即線程不會緩存static變量
  • 因為volatile的禁止指令重排序,在第10步中,初始化instance對象并非原子操作,它包括:1.開辟堆內存2.調用構造方法初始化對象;如果沒有volatile關鍵字,且在高并發情況下,如果某個線程開辟了堆內存后還未完成構造時,此時另一個線程進入外層判空后后發現instance對象非空,就返回了未構造完全的instance對象;volatile的意義在于能夠禁止對當前對象進行指令的重排序,也就是happen-before原則的關于volatile的一條:"volatile變量規則:對一個變量的寫操作先行發生于后面對這個變量的讀操作",也就是說,無論什么情況,對于volatile變量的寫操作必須在完成后才能讀取,所以不會出現未完成構造就讀取的情況;但是volatile不能保證同時對變量的寫操作也是有序的,也就是volatile不能保證原子性
  • ? ?? ?以上就是雙重校驗鎖會失效的原因,不過還好在JDK1.5及之后版本增加了volatile關鍵字volatile的一個語義是禁止指令重排序優化,也就保證了instance變量被賦值的時候對象已經是初始化過的,從而避免了上面說到的問題。代碼如下:

    [java]?view plain?copy

    1.? public?class?Singleton?{??

    2.? ????private?static?volatile?Singleton?instance?=?null;??

    3.? ????private?Singleton(){}??

    4.? ????public?static?Singleton?getInstance()?{??

    5.? ????????if?(instance?==?null)?{??

    6.? ????????????synchronized?(Singleton.class)?{??

    7.? ????????????????if?(instance?==?null)?{??

    8.? ????????????????????instance?=?new?Singleton();??

    9.? ????????????????}??

    10. ????????????}??

    11. ????????}??

    12. ????????return?instance;??

    13. ????}??

    14. }??

    ?

    JDK 版本:JDK1.5 起

    是否 Lazy 初始化:

    是否多線程安全:

    實現難度:較復雜

    描述:這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。
    getInstance() 的性能對應用程序很關鍵。

    ?

    /靜態內部類

    是否Lazy 初始化:

    是否多線程安全:

    實現難度:一般

    描述:這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用于靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。
    這種方式同樣利用了 classloder 機制來保證初始化 instance 時只有一個線程,?Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有通過顯式調用 getInstance 方法時,才會顯式裝載SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化 instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理。

    代碼實例:

    public class Singleton {? ????private static class SingletonHolder {? //靜態內部類 ????private static final Singleton INSTANCE = new Singleton();? ????}? ????private Singleton (){}? ????public static Singleton getInstance() {? ????return SingletonHolder.INSTANCE;? ????}? }??

    ?

    5、枚舉(面試還是寫3 4種最好了,這種面試可以提一下)

    ? ?? ?再來看本文要介紹的最后一種實現方式:枚舉。

    [java]?view plain?copy

    1.? public?enum?Singleton{??

    2.? ????instance;??

    3.? ????public?void?whateverMethod(){}??????

    4.? }??

    ? ?? ?上面提到的四種實現單例的方式都有共同的缺點:

    1)需要額外的工作來實現序列化,否則每次反序列化一個序列化的對象時都會創建一個新的實例。

    2)可以使用反射強行調用私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。

    ? ?? ?而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器之外,還提供了自動序列化機制,防止反序列化的時候創建新的對象。因此,《Effective Java》作者推薦使用的方法。不過,在實際工作中,很少看見有人這么寫。


    單例模式的五種實現方式總結:

    ?

    ?

    ?

    ?

    ?

    總結

    以上是生活随笔為你收集整理的创建型模式:单例模式(懒汉+饿汉+双锁校验+内部类+枚举)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。