亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java中的單例模式詳解(完整篇)

 更新時(shí)間:2021年11月03日 12:08:21   作者:看山  
Java單例模式應(yīng)該是看起來(lái)以及用起來(lái)簡(jiǎn)單的一種設(shè)計(jì)模式,但是就實(shí)現(xiàn)方式以及原理來(lái)說(shuō),也并不淺顯,下面這篇文章主要給大家介紹了關(guān)于Java中單例模式的相關(guān)資料,需要的朋友可以參考下

前言

個(gè)人認(rèn)為單例模式是設(shè)計(jì)模式中最簡(jiǎn)單也是最常用的一種,是對(duì)有限資源合理利用的一種方式。這個(gè)模式看似簡(jiǎn)單,但是其中蘊(yùn)含了關(guān)于并發(fā)、類加載、序列化等一系列深層次的知識(shí),如果理解不夠深,就有可能在高并發(fā)時(shí)遇到難以預(yù)期的異常,或者會(huì)造成資源浪費(fèi)。

所以本文會(huì)從將目前Java領(lǐng)域最常用的幾種單例模式列出來(lái),供大家參考。

WHAT

維基百科給出了解釋、實(shí)現(xiàn)的思路以及應(yīng)該注意的地方:

單例模式,也叫單子模式,是一種常用的軟件設(shè)計(jì)模式,屬于創(chuàng)建型模式的一種。在應(yīng)用這個(gè)模式時(shí),單例對(duì)象的類必須保證只有一個(gè)實(shí)例存在。
實(shí)現(xiàn)單例模式的思路是:一個(gè)類能返回對(duì)象一個(gè)引用(永遠(yuǎn)是同一個(gè))和一個(gè)獲得該實(shí)例的方法(必須是靜態(tài)方法,通常使用getInstance這個(gè)名稱);當(dāng)我們調(diào)用這個(gè)方法時(shí),如果類持有的引用不為空就返回這個(gè)引用,如果類保持的引用為空就創(chuàng)建該類的實(shí)例并將實(shí)例的引用賦予該類保持的引用;同時(shí)我們還將該類的構(gòu)造函數(shù)定義為私有方法,這樣其他處的代碼就無(wú)法通過(guò)調(diào)用該類的構(gòu)造函數(shù)來(lái)實(shí)例化該類的對(duì)象,只有通過(guò)該類提供的靜態(tài)方法來(lái)得到該類的唯一實(shí)例。
單例模式在多線程的應(yīng)用場(chǎng)合下必須小心使用。如果當(dāng)唯一實(shí)例尚未創(chuàng)建時(shí),有兩個(gè)線程同時(shí)調(diào)用創(chuàng)建方法,那么它們同時(shí)沒有檢測(cè)到唯一實(shí)例的存在,從而同時(shí)各自創(chuàng)建了一個(gè)實(shí)例,這樣就有兩個(gè)實(shí)例被構(gòu)造出來(lái),從而違反了單例模式中實(shí)例唯一的原則。解決這個(gè)問(wèn)題的辦法是為指示類是否已經(jīng)實(shí)例化的變量提供一個(gè)互斥鎖(雖然這樣會(huì)降低效率)。

類圖是:

WHY

正如定義所說(shuō),單例模式就是整個(gè)內(nèi)存模型中,只有一個(gè)實(shí)例。實(shí)例少了,內(nèi)存占用就少。同時(shí),只有一個(gè)實(shí)例,也就只需要構(gòu)建一個(gè)對(duì)象,計(jì)算就少。對(duì)于構(gòu)造過(guò)程中需要大量計(jì)算或者占用大量資源的對(duì)象,只創(chuàng)建一次,就減少了資源占用和內(nèi)存占用。

餓漢式

餓漢式是最簡(jiǎn)單的一種實(shí)現(xiàn),在類裝載過(guò)程中,完成實(shí)例化,避免多線程問(wèn)題。

實(shí)現(xiàn)一:靜態(tài)實(shí)例參數(shù)與靜態(tài)代碼塊

public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

根據(jù)java的特性,餓漢式還可以變種寫法,有的地方稱為靜態(tài)代碼塊方式:

public class EagerSingleton {
    private static EagerSingleton INSTANCE = null;

    static {
        INSTANCE = new EagerSingleton();
    }

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

這兩種方式只是在寫法上的區(qū)別,優(yōu)缺點(diǎn)沒有區(qū)別,只是借助Java語(yǔ)言特性的不同寫法,所以歸為一類。

餓漢式有兩個(gè)明顯的缺點(diǎn):

  1. 類裝載過(guò)程即完成實(shí)例化,如果整個(gè)應(yīng)用生命周期內(nèi),實(shí)例沒有使用,也就是浪費(fèi)資源了。
  2. 因?yàn)闆]有辦法向構(gòu)造函數(shù)傳遞不同的參數(shù),如果需要通過(guò)個(gè)性化參數(shù)定制實(shí)例時(shí),這種方式就不支持了。

實(shí)現(xiàn)二:靜態(tài)內(nèi)部類

針對(duì)餓漢式第一個(gè)缺點(diǎn),我們可以借助靜態(tài)內(nèi)部類的方式,將對(duì)象實(shí)例化的時(shí)間延后。

public class EagerSingleton {
    private EagerSingleton() {
    }

    private static class EagerSingletonInstance {
        private static final EagerSingleton INSTANCE = new EagerSingleton();
    }

    public static EagerSingleton getInstance() {
        return EagerSingletonInstance.INSTANCE;
    }
}

但是,依然不能很好的解決第二個(gè)缺點(diǎn),如果需要根據(jù)不同的參數(shù)實(shí)現(xiàn)不同的實(shí)例,可以采用下面說(shuō)的懶漢式實(shí)現(xiàn)。

懶漢式

懶漢式比餓漢式的一個(gè)優(yōu)點(diǎn),就是能夠在使用的時(shí)候再進(jìn)行實(shí)例化。但是,餡餅總是要伴隨著陷阱,懶漢式寫法有更多的坑,一不小心就摔著了。

錯(cuò)誤一:?jiǎn)尉€程實(shí)現(xiàn)

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

之所以定義為單線程實(shí)現(xiàn),是因?yàn)?INSTANCE==null這個(gè)判斷,一個(gè)線程通過(guò)這個(gè)判斷,開始進(jìn)行對(duì)象實(shí)例化,但是還沒有實(shí)例化完成,另一個(gè)線程又來(lái)了,這個(gè)時(shí)候,對(duì)象還沒有實(shí)例化,就也會(huì)開始進(jìn)行實(shí)例化,造成不必要的浪費(fèi)。

錯(cuò)誤二:同步方法

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static synchronized LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
}

這種方式解決了多線程的問(wèn)題,但是也引入了新的性能問(wèn)題:太慢。synchronized把整個(gè)方法包起來(lái),也就是每個(gè)線程進(jìn)入的時(shí)候,都需要等待其他線程結(jié)束調(diào)用,才能拿到實(shí)例,在性能敏感的場(chǎng)景,是比較致命的。

錯(cuò)誤三:同步代碼塊之單次檢查

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                INSTANCE = new LazySingleton();
            }
        }
        return INSTANCE;
    }
}

這種寫法看似將同步代碼縮小,但也縮小了多線程保障,也犯了第一種寫法的錯(cuò)誤,屬于沒有對(duì)多線程有基本了解寫出的低級(jí)錯(cuò)誤代碼。

錯(cuò)誤四:同步代碼塊之雙重檢查

public class LazySingleton {
    private static LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

這種寫法在一定程度上屬于正確的寫法,雙重判斷可以很好的實(shí)現(xiàn)線程安全和延遲加載。如果到這里就結(jié)束,那就是謬以千里的毫厘之差。

雙重檢查和同步代碼塊都沒有問(wèn)題,問(wèn)題出在 INSTANCE=newLazySingleton()這句話。在JVM中,為了充分利用CPU計(jì)算能力,會(huì)進(jìn)行重排序優(yōu)化, INSTANCE=newLazySingleton()做了三件事:

  1. 為 INSTANCE 初始化??臻g
  2. 為 LazySingleton 分配內(nèi)存空間,實(shí)例化對(duì)象
  3. INSTANCE 指向 LazySingleton 實(shí)例分配的內(nèi)存空間

因?yàn)橹嘏判騼?yōu)化的存在,真正執(zhí)行的過(guò)程中,可能會(huì)出現(xiàn)1-2-3的順序,也可能出現(xiàn)1-3-2的順序。如果是1-3-2,INSTANCE 指向了 LazySingleton 實(shí)例分配的內(nèi)存空間后,就不是null,另外一個(gè)線程進(jìn)入判斷null時(shí),就會(huì)直接返回 INSTANCE,但是這個(gè)時(shí)候 LazySingleton 實(shí)例化還沒有完成,就可能出現(xiàn)意想不到的異常。

正確:雙重檢查+阻止重排序

public class LazySingleton {
    private static volatile LazySingleton INSTANCE = null;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new LazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

這種寫法比上面那種,就差在 volatile這個(gè)關(guān)鍵字。

枚舉

懶漢式和餓漢式都能夠適用于多線程并發(fā)場(chǎng)景,但是通過(guò)反序列化或反射可以實(shí)例化對(duì)象,這樣依然不能滿足單例模式的要求,所以可以借助枚舉實(shí)現(xiàn),枚舉可以完美避免多線程并發(fā)問(wèn)題,而且可以防止反序列化和反射創(chuàng)建新對(duì)象。第一次看到這樣定義單例模式,是在《Effective Java》中,多讀經(jīng)典書還是挺好的。

public enum EnumSingleton {
    INSTANCE;

    public void method1() {
        // do something
    }

    public Object method2() {
        // do something and return something else
        return new Object();
    }
}

在開發(fā)實(shí)踐中,枚舉可以滿足絕大部分場(chǎng)景,而且寫法簡(jiǎn)單,定義單例的邏輯只需要三行代碼,簡(jiǎn)潔而不簡(jiǎn)單,三行代碼可以保證線程安全。同時(shí)枚舉的反序列化只是通過(guò)name查找對(duì)象,不會(huì)產(chǎn)生新的對(duì)象;根據(jù)JVM規(guī)范,通過(guò)反射創(chuàng)建枚舉對(duì)象時(shí),會(huì)拋出 IllegalArgumentException異常。這樣,相當(dāng)于通過(guò)語(yǔ)法糖防止反序列化和反射破壞單例。

場(chǎng)景

  1. 無(wú)狀態(tài)工具類:這種工具類不需要記錄狀態(tài),只保證正確的應(yīng)用就行,可以通過(guò)單例模式來(lái)定義。
  2. 數(shù)據(jù)共享:即多個(gè)不相關(guān)的兩個(gè)線程或者進(jìn)程之間實(shí)現(xiàn)通信。因?yàn)槭且粋€(gè)實(shí)例,如果它的屬性或者變量值被修改,所有引用都是同時(shí)修改的,當(dāng)然需要 volatile 來(lái)定義變量。比如網(wǎng)站的計(jì)數(shù)器。
  3. 日志應(yīng)用:通常應(yīng)用會(huì)向日志文件寫日志信息,為了實(shí)時(shí)向文件寫,通常會(huì)使用單例模式,保證有一個(gè)實(shí)例持有文件,然后進(jìn)行操作。
  4. 數(shù)據(jù)庫(kù)連接池:數(shù)據(jù)庫(kù)連接是一種數(shù)據(jù)庫(kù)資源,使用數(shù)據(jù)庫(kù)連接池,主要是節(jié)省打開或者關(guān)閉數(shù)據(jù)庫(kù)連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,通過(guò)單例模式來(lái)維護(hù),就可以大大降低這種損耗。
  5. Web應(yīng)用的配置對(duì)象:讀取文件需要消耗時(shí)間,如果讀取大文件,消耗的時(shí)間和資源更久,所以通過(guò)單例模式可以大大降低消耗。
  6. 。。。

單例模式的場(chǎng)景還是比較多的,這里只是列出里幾個(gè)簡(jiǎn)單的應(yīng)用場(chǎng)景,算是拋磚引玉,如果看官們有什么其他應(yīng)用場(chǎng)景,可以在說(shuō)一說(shuō)

總結(jié)

到此這篇關(guān)于Java中單例模式的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)的雙向匹配分詞算法示例

    Java實(shí)現(xiàn)的雙向匹配分詞算法示例

    這篇文章主要介紹了Java實(shí)現(xiàn)的雙向匹配分詞算法,結(jié)合完整實(shí)例形式詳細(xì)分析了雙向匹配分詞算法的原理與java實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-12-12
  • Spring session 獲取當(dāng)前賬戶登錄數(shù)的實(shí)例代碼

    Spring session 獲取當(dāng)前賬戶登錄數(shù)的實(shí)例代碼

    這篇文章主要介紹了Spring session 獲取當(dāng)前賬戶登錄數(shù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • 詳解SpringMVC Controller介紹及常用注解

    詳解SpringMVC Controller介紹及常用注解

    本篇文章主要介紹了SpringMVC Controller介紹及常用注解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • Spring中bean的初始化和銷毀幾種實(shí)現(xiàn)方式詳解

    Spring中bean的初始化和銷毀幾種實(shí)現(xiàn)方式詳解

    這篇文章主要介紹了Spring中bean的初始化和銷毀幾種實(shí)現(xiàn)方式詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Java中如何對(duì)字符串進(jìn)行utf-8編碼

    Java中如何對(duì)字符串進(jìn)行utf-8編碼

    這篇文章主要介紹了Java中如何對(duì)字符串進(jìn)行utf-8編碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Java加載資源文件時(shí)的路徑問(wèn)題的解決辦法

    Java加載資源文件時(shí)的路徑問(wèn)題的解決辦法

    今天偶然看到一篇關(guān)于tomcat加載servlet的文章,不由得想起了java加載資源文件的路徑問(wèn)題,資源文件可以使xml,properties,圖片等,可以是任何文件
    2013-04-04
  • log4j使用詳細(xì)解析

    log4j使用詳細(xì)解析

    Log4j 除了可以記錄程序運(yùn)行日志信息外還有一重要的功能就是用來(lái)顯示調(diào)試信息。下面通過(guò)本文給大家介紹log4j使用詳細(xì)解析,感興趣的朋友一起看看吧
    2017-10-10
  • SpringBoot整合Kaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能

    SpringBoot整合Kaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能

    這篇文章主要介紹了SpringBoot整合Kaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • springboot如何使用thymeleaf模板訪問(wèn)html頁(yè)面

    springboot如何使用thymeleaf模板訪問(wèn)html頁(yè)面

    springboot中推薦使用thymeleaf模板,使用html作為頁(yè)面展示。那么如何通過(guò)Controller來(lái)訪問(wèn)來(lái)訪問(wèn)html頁(yè)面呢?下面通過(guò)本文給大家詳細(xì)介紹,感興趣的朋友跟隨腳本之家小編一起看看吧
    2018-05-05
  • SpringBoot注冊(cè)第三方Bean的方法總結(jié)

    SpringBoot注冊(cè)第三方Bean的方法總結(jié)

    眾所周知,SpringBoot默認(rèn)會(huì)掃描啟動(dòng)類所在的包及其子包,一般我們都是在需要的類上通過(guò)注解的方式去將Bean注冊(cè)交給IOC進(jìn)行管理,但是注冊(cè)第三方Bean的方案卻不支持,所以本文給大家介紹了SpringBoot注冊(cè)第三方Bean的方法,需要的朋友可以參考下
    2024-01-01

最新評(píng)論