Java多線程案例之單例模式懶漢+餓漢+枚舉
前言:
本篇文章將介紹Java多線程中的幾個典型案例之單例模式,所謂單例模式,就是一個類只有一個實例對象,本文將著重介紹在多線程的背景下,單例模式的簡單實現(xiàn)。
1.單例模式概述
單例模式,是一種常用的軟件設(shè)計模式。在它的核心結(jié)構(gòu)中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統(tǒng)中,應(yīng)用該模式的類一個類只有一個實例,即一個類只有一個對象實例。
單例模式有兩種典型的實現(xiàn),一是餓漢模式,二是懶漢模式,餓漢模式中的“餓”并不是真的表示“餓”,更加準確的來說應(yīng)該是表示“急”,那就是一個單例類還沒被使用,它的單例對象就已經(jīng)創(chuàng)建好了,而懶漢模式,要等到使用這個單例類時才創(chuàng)建單例對象。
單例模式中的單例類,只能擁有一個實例對象,又static
修飾的成員是屬于類的,也就是只有一份,所以我們可以使用static
修飾的成員變量保存實例對象的引用。
2.單例模式的簡單實現(xiàn)
2.1餓漢模式
由于單例模式中,一個類只能擁有一個實例對象,所以需要將類構(gòu)造方法封裝,防止類被創(chuàng)建多個實例對象,但是在使用該類時必須要得到該類的實例對象,因此我們得創(chuàng)建一個獲取該唯一實例對象的方法getInstance
。
而對于該類的實例對象,在類中我們可以使用屬于類的成員變量來保存(即static
成員變量)。
//單例模式 - 餓漢模式 class HungrySingleton { //1.使用一個變量來保存該類唯一的實例,因為單例模式在一個程序中只能擁有一個實例,由于static成員只有一份,我們可以使用static變量來保存 private static final HungrySingleton instance = new HungrySingleton(); //2.封裝構(gòu)造方法,防止該類被實例出新的對象 private HungrySingleton() {} //3.獲取該類的唯一實例對象 public HungrySingleton getInstance() { return instance; } }
多線程情況下,對于上述簡單實現(xiàn)的餓漢式單例模式,只需要考慮getInstance
方法是否線程安全即可,由于該方法就一句返回語句,即一次讀操作,而讀操作是線程安全的,所以getInstance
方法也就是線程安全的,綜上餓漢式單例模式是線程安全的。
2.2懶漢模式
懶漢模式相比于餓漢模式,區(qū)別就是實例對象創(chuàng)建時機不同,懶漢模式需要等到第一次使用時才創(chuàng)建實例對象,所以僅僅只需要修改獲取對象的方法即可。
不考慮多線程情況,懶漢模式實現(xiàn)代碼如下:
//單例模式 - 懶漢模式 class SlackerSingleton { //1.使用一個變量來保存該類唯一的實例,因為單例模式在一個程序中只能擁有一個實例,由于static成員只有一份,我們可以使用static變量來保存 //懶漢單例模式是在使用的時候創(chuàng)建對象,因此初始時對象不應(yīng)該被創(chuàng)建 private static SlackerSingleton instance; //2.封裝構(gòu)造方法,防止該類被實例出新的對象 private SlackerSingleton() {} //3.獲取該類的唯一對象,如果沒有就創(chuàng)建 public SlackerSingleton getInstance() { if (instance == null) { instance = new SlackerSingleton(); } return instance; } }
多線程情況下,由于getInstance
方法中存在兩次讀(一次判斷一次返回)操作一次寫操作(修改intsance
變量的值),instance
變量為初始化時(即instance=null
)可能會存在多個線程進入判斷語句,這樣該類可能會被實例出多個對象,所以上述實現(xiàn)的懶漢式單例模式是線程不安全的。
造成線程不安全的代碼段為if語句里面的讀操作和instance
的修改操作,所以我們需要對這段代碼進行加鎖,然后就得到了線程安全的懶漢模式:
//多線程情況下餓漢模式獲取對象時只讀不修改,所以是線程安全的 //多線程情況下懶漢模式獲取對象時存在兩次讀操作,分別為判斷instance是否為null和返回instance,除了讀操作還存在修改操作,即新建對象并使instance指向該對象 //懶漢模式對象還未初始化的時候,可能會存在多個線程進入判斷語句,會導(dǎo)致實例出多個對象,因此懶漢單例模式是線程不安全的。 //線程安全單例模式 - 懶漢模式 class SafeSlackerSingleton { //1.使用一個變量來保存該類唯一的實例,因為單例模式在一個程序中只能擁有一個實例,由于static成員只有一份,我們可以使用static變量來保存 //懶漢單例模式是在使用的時候創(chuàng)建對象,因此初始時對象不應(yīng)該被創(chuàng)建 private static SafeSlackerSingleton instance; //2.封裝構(gòu)造方法,防止該類被實例出新的對象 private SafeSlackerSingleton() {} //3.獲取該類的唯一對象,如果沒有就創(chuàng)建 public SafeSlackerSingleton getInstance() { synchronized (SafeSlackerSingleton.class) { if (instance == null) { instance = new SafeSlackerSingleton(); } } return instance; } }
但是!上述線程安全問題只出現(xiàn)在instance
沒有初始化的時候,如果instance
已經(jīng)初始化了,那個判斷語句就是個擺設(shè),就和餓漢模式一樣,就是線程安全的了,如果按照上面的代碼處理線程安全問題,不論instance
是否已經(jīng)初始化,都要進行加鎖,因此會使鎖競爭加劇,消耗沒有必要消耗的資源,所以在加鎖前需要先判斷一下instance
是否已經(jīng)初始化,如果為初始化就進行加鎖。
按照上述方案得到以下關(guān)于獲取對象的方法代碼:
public SafeSlackerSingletonPlus getInstance() { //判斷instance是否初始化,如果已經(jīng)初始化了,那么該方法只有兩個讀操作,本身就是線程安全的,不需要加鎖了,這樣能減少鎖競爭,提高效率 if (instance == null) { synchronized (SafeSlackerSingletonPlus.class) { if (instance == null) { instance = new SafeSlackerSingletonPlus(); } } } return instance; }
到這里線程安全的問題是解決了,但是別忘了編譯器它是不信任你的,它會對你寫的代碼進行優(yōu)化! 上面所寫的代碼需要判斷instance==null
,而多線程情況下,很可能頻繁進行判斷,這時候線程不會去讀內(nèi)存中的數(shù)據(jù),而會直接去寄存器讀數(shù)據(jù),這時候instance
值變化時,線程完全感知不到!造成內(nèi)存可見性問題,為了解決該問題需要使用關(guān)鍵字volatile
修飾instance
變量,防止編譯器優(yōu)化,從而保證內(nèi)存可見性。
//線程安全優(yōu)化單例模式 - 懶漢模式 class SafeSlackerSingletonPlus { //1.使用一個變量來保存該類唯一的實例,因為單例模式在一個程序中只能擁有一個實例,由于static成員只有一份,我們可以使用static變量來保存 //懶漢單例模式是在使用的時候創(chuàng)建對象,因此初始時對象不應(yīng)該被創(chuàng)建 private static volatile SafeSlackerSingletonPlus instance; //2.封裝構(gòu)造方法,防止該類被實例出新的對象 private SafeSlackerSingletonPlus() {} //3.獲取該類的唯一對象,如果沒有就創(chuàng)建 public SafeSlackerSingletonPlus getInstance() { //判斷instance是否初始化,如果已經(jīng)初始化了,那么該方法只有兩個讀操作,本身就是線程安全的,不需要加鎖了,這樣能減少鎖競爭,提高效率 //如果線程很多,頻繁進行外層或內(nèi)層if判斷,可能會引發(fā)內(nèi)層可見性問題,因此要給instan變量加上volatile if (instance == null) { synchronized (SafeSlackerSingletonPlus.class) { if (instance == null) { instance = new SafeSlackerSingletonPlus(); } } } return instance; } }
2.3枚舉實現(xiàn)單例模式
除了使用餓漢和懶漢模式還可以使用枚舉的方式實現(xiàn),在《Effective Java》書中有這樣一句話:單元素的枚舉類型已經(jīng)成為實現(xiàn)Singleton的最佳方法。 因為枚舉就是一個天然的單例,并且枚舉類型通過反射都無法獲取封裝的私有變量,非常安全。
//單元素的枚舉類型已經(jīng)成為實現(xiàn)Singleton的最佳方法 enum EnumSingleton { INSTANCE; public void doSomething() { System.out.println("完成一些任務(wù)!"); } }
使用方式:
public class Singleton { public static void main(String[] args) { EnumSingleton.INSTANCE.doSomething(); } }
運行結(jié)果:
好了,有關(guān)多線程單例模式問題就討論到這里了,你學會了嗎?
到此這篇關(guān)于Java多線程案例之單例模式懶漢+餓漢+枚舉的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java編程中static關(guān)鍵字和final關(guān)鍵字的使用
這篇文章主要介紹了詳解Java編程中static關(guān)鍵字和final關(guān)鍵字的使用,是Java入門學習中的基礎(chǔ)知識,需要的朋友可以參考下2015-09-09Redisson 分布式延時隊列 RedissonDelayedQueue 運行流程
這篇文章主要介紹了Redisson分布式延時隊列 RedissonDelayedQueue運行流程,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09Springboot+Redis實現(xiàn)API接口限流的示例代碼
本文主要介紹了Springboot+Redis實現(xiàn)API接口限流的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07Java Swing組件布局管理器之FlowLayout(流式布局)入門教程
這篇文章主要介紹了Java Swing組件布局管理器之FlowLayout(流式布局),結(jié)合實例形式分析了Swing組件布局管理器FlowLayout流式布局的常用方法及相關(guān)使用技巧,需要的朋友可以參考下2017-11-11如何將eclipse項目導(dǎo)入到idea的方法步驟(圖文)
這篇文章主要介紹了如何將eclipse項目導(dǎo)入到idea的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03