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)多線程單例模式問題就討論到這里了,你學(xué)會了嗎?
到此這篇關(guān)于Java多線程案例之單例模式懶漢+餓漢+枚舉的文章就介紹到這了,更多相關(guān)Java單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java判斷范圍型的數(shù)據(jù)是否存在重疊的方法
遇到了個問題,同一天可以輸入多個時間段,但是每個時間段的時間不能出現(xiàn)重疊,這不就是判斷數(shù)據(jù)返回是否有重疊的變種嗎,所以本文給大家介紹了Java判斷范圍型的數(shù)據(jù)是否存在重疊的方法,需要的朋友可以參考下2024-07-07
@DS注解的使用,動態(tài)數(shù)據(jù)源,事務(wù)詳解
在項目中使用多數(shù)據(jù)源時,可以借助苞米豆的dynamic-datasource-spring-boot-starter進行配置,首先需引入相應(yīng)的jar包,并在application.yml中設(shè)置主從數(shù)據(jù)源,其中一般選擇master作為默認數(shù)據(jù)源,在實現(xiàn)類中通過@DS注解指定數(shù)據(jù)源2024-09-09
java組件smartupload實現(xiàn)上傳文件功能
這篇文章主要為大家詳細介紹了java組件smartupload實現(xiàn)上傳文件功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10
深入探究SpringBoot中的Elasticsearch自動配置原理及用法
SpringBoot中的Elasticsearch自動配置為我們提供了一種快速集成Elasticsearch的方式,使我們可以在SpringBoot應(yīng)用程序中輕松地使用Elasticsearch,本文將介紹Spring Boot中的Elasticsearch自動配置的作用、原理和使用方法2023-07-07
Spring使用AspectJ注解和XML配置實現(xiàn)AOP
這篇文章主要介紹了Spring使用AspectJ注解和XML配置實現(xiàn)AOP的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10
Java?Collections.sort()實現(xiàn)List排序的默認方法和自定義方法
這篇文章主要介紹了Java?Collections.sort()實現(xiàn)List排序的默認方法和自定義方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2017-06-06

