Java Volatile應(yīng)用單例模式實(shí)現(xiàn)過程解析
單例模式
回顧一下,單線程下的單例模式代碼
餓漢式
- 構(gòu)造器私有化
- 自行創(chuàng)建,并且用靜態(tài)變量保存static
- 向外提供這個(gè)實(shí)例 public
- 強(qiáng)調(diào)這是一個(gè)單例,用final
public class sington(){ public final static INSTANCE = new singleton(); private singleton(){} }
第二種:jdk1.5之后用枚舉類型
枚舉類型:表示該類型的對象是有限的幾個(gè)
我們可以限定為1個(gè),就稱了單例
public enum Singleto{ INSTANCE }
第三種靜態(tài)代碼塊
public class Singleton{ public final static INSTANCE; static{ INSTANCE = new Singleton(); } private Singleton(){} }
懶漢式構(gòu)造器私有化
用一個(gè)靜態(tài)變量保存這個(gè)唯一實(shí)例
提供一個(gè)靜態(tài)方法,獲取這個(gè)實(shí)例
public class Singleton{ private static Singleton INSTANCE; private Singleton(){} public static Singleton getInstance(){ if(instance==null){ INSTANCE = new Singleton(); } return INSTANCE; } }
public class SingletonDemo { private static SingletonDemo instance = null; private SingletonDemo () { System.out.println(Thread.currentThread().getName() + "\t 我是構(gòu)造方法SingletonDemo"); } public static SingletonDemo getInstance() { if(instance == null) { instance = new SingletonDemo(); } return instance; } public static void main(String[] args) { // 這里的 == 是比較內(nèi)存地址 System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); } }
最后輸出結(jié)果:
但是在多線程的環(huán)境下,我們的單例模式是否還是同一個(gè)對象了
public class SingletonDemo { private static SingletonDemo instance = null; private SingletonDemo () { System.out.println(Thread.currentThread().getName() + "\t 我是構(gòu)造方法SingletonDemo"); } public static SingletonDemo getInstance() { if(instance == null) { instance = new SingletonDemo(); } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { SingletonDemo.getInstance(); }, String.valueOf(i)).start(); } } }
從下面的結(jié)果我們可以看出,我們通過SingletonDemo.getInstance() 獲取到的對象,并不是同一個(gè),而是被下面幾個(gè)線程都進(jìn)行了創(chuàng)建,那么在多線程環(huán)境下,單例模式如何保證呢?
解決方法一
引入synchronized關(guān)鍵字
public synchronized static SingletonDemo getInstance() { if(instance == null) { instance = new SingletonDemo(); } return instance; }
輸出結(jié)果:
我們能夠發(fā)現(xiàn),通過引入Synchronized關(guān)鍵字,能夠解決高并發(fā)環(huán)境下的單例模式問題
但是synchronized屬于重量級的同步機(jī)制,它只允許一個(gè)線程同時(shí)訪問獲取實(shí)例的方法,但是為了保證數(shù)據(jù)一致性,而減低了并發(fā)性,因此采用的比較少
解決方法二
通過引入DCL Double Check Lock雙端檢鎖機(jī)制
public static SingletonDemo getInstance() { if(instance == null) { // 同步代碼段的時(shí)候,進(jìn)行檢測 synchronized (SingletonDemo.class) { if(instance == null) { instance = new SingletonDemo(); } } } return instance; }
最后輸出的結(jié)果為:
從輸出結(jié)果來看,確實(shí)能夠保證單例模式的正確性,但是上面的方法還是存在問題的
DCL(雙端檢鎖)機(jī)制不一定是線程安全的,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因是在某一個(gè)線程執(zhí)行到第一次檢測的時(shí)候,讀取到 instance 不為null,instance的引用對象可能沒有完成實(shí)例化。因?yàn)?instance = new SingletonDemo();可以分為以下三步進(jìn)行完成:
- memory = allocate(); // 1、分配對象內(nèi)存空間
- instance(memory); // 2、初始化對象
- instance = memory; // 3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時(shí)instance != null
但是我們通過上面的三個(gè)步驟,能夠發(fā)現(xiàn),步驟2 和 步驟3之間不存在 數(shù)據(jù)依賴關(guān)系,而且無論重排前 還是重排后,程序的執(zhí)行結(jié)果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的。
- memory = allocate(); // 1、分配對象內(nèi)存空間
- instance = memory; // 3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時(shí)instance != null,但是對象還沒有初始化完成
- instance(memory); // 2、初始化對象
這樣就會造成什么問題呢?
也就是當(dāng)我們執(zhí)行到重排后的步驟2,試圖獲取instance的時(shí)候,會得到null,因?yàn)閷ο蟮某跏蓟€沒有完成,而是在重排后的步驟3才完成,因此執(zhí)行單例模式的代碼時(shí)候,就會重新在創(chuàng)建一個(gè)instance實(shí)例
指令重排只會保證串行語義的執(zhí)行一致性(單線程),但并不會關(guān)系多線程間的語義一致性
所以當(dāng)一條線程訪問instance不為null時(shí),由于instance實(shí)例未必已初始化完成,這就造成了線程安全的問題
所以需要引入volatile,來保證出現(xiàn)指令重排的問題,從而保證單例模式的線程安全性
private static volatile SingletonDemo instance = null;
最終代碼
public class SingletonDemo { private static volatile SingletonDemo instance = null; private SingletonDemo () { System.out.println(Thread.currentThread().getName() + "\t 我是構(gòu)造方法SingletonDemo"); } public static SingletonDemo getInstance() { if(instance == null) { // a 雙重檢查加鎖多線程情況下會出現(xiàn)某個(gè)線程雖然這里已經(jīng)為空,但是另外一個(gè)線程已經(jīng)執(zhí)行到d處 synchronized (SingletonDemo.class) //b { //c不加volitale關(guān)鍵字的話有可能會出現(xiàn)尚未完全初始化就獲取到的情況。原因是內(nèi)存模型允許無序?qū)懭? if(instance == null) { // d 此時(shí)才開始初始化 instance = new SingletonDemo(); } } } return instance; } public static void main(String[] args) { // // 這里的 == 是比較內(nèi)存地址 // System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); // System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); // System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); // System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance()); for (int i = 0; i < 10; i++) { new Thread(() -> { SingletonDemo.getInstance(); }, String.valueOf(i)).start(); } } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot整合FastDFS中間件實(shí)現(xiàn)文件分布管理
FastDFS是一個(gè)開源的輕量級分布式文件系統(tǒng),它對文件進(jìn)行管理,功能包括:文件存儲、文件同步、文件上傳、文件下載等,解決了大容量存儲和負(fù)載均衡的問題,本文介紹了SpringBoot整合FastDFS中間件實(shí)現(xiàn)文件分布管理,需要的朋友可以參考下2024-08-08Java如何基于ProcessBuilder類調(diào)用外部程序
這篇文章主要介紹了Java如何基于ProcessBuilder類調(diào)用外部程序,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01springboot做代理分發(fā)服務(wù)+代理鑒權(quán)的實(shí)現(xiàn)過程
這篇文章主要介紹了springboot做代理分發(fā)服務(wù)+代理鑒權(quán)的實(shí)現(xiàn)過程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01解決nacos的yml配置文件解析@開頭的值啟動報(bào)錯問題
這篇文章主要介紹了解決nacos的yml配置文件解析@開頭的值啟動報(bào)錯問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07Java實(shí)現(xiàn)將Word轉(zhuǎn)換成Html的示例代碼
在業(yè)務(wù)中,常常會需要在瀏覽器中預(yù)覽Word文檔,或者需要將Word文檔轉(zhuǎn)成HTML文件保存,本文主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)Word轉(zhuǎn)換成Html的相關(guān)方法,希望對大家有所幫助2024-02-02Mybatis foreach用法解析--對于list和array
這篇文章主要介紹了Mybatis foreach用法解析--對于list和array,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03前端發(fā)送的請求Spring如何返回一個(gè)文件詳解
這篇文章主要給大家介紹了關(guān)于前端發(fā)送的請求Spring如何返回一個(gè)文件的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-09-09Java ByteBuffer網(wǎng)絡(luò)編程用法實(shí)例解析
這篇文章主要介紹了Java ByteBuffer網(wǎng)絡(luò)編程用法實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10