Java單例模式中的線程安全問(wèn)題
一. 使用多線程需要考慮的因素
提高效率:使用多線程就是為了充分利用CPU資源,提高任務(wù)的效率
線程安全:使用多線程最基本的就是保障線程安全問(wèn)題
所以我們?cè)谠O(shè)計(jì)多線程代碼的時(shí)候就必須在滿足線程安全的前提下盡可能的提高任務(wù)執(zhí)行的效
故:
加鎖細(xì)粒度化:加鎖的代碼少一點(diǎn),讓其他代碼可以并發(fā)并行的執(zhí)行
??考慮線程安全:
沒(méi)有操作共享變量的代碼沒(méi)有安全問(wèn)題
對(duì)共享變量的讀,使用volatile修飾變量即可
對(duì)共享變量的寫,使用synchronized加鎖
??二. 單例模式
單例模式能保證某個(gè)類在程序中只存在唯一一份實(shí)例,而不會(huì)創(chuàng)建出多個(gè)實(shí)例
例如:DataSource(數(shù)據(jù)連接池),一個(gè)數(shù)據(jù)庫(kù)只需要一個(gè)連接池對(duì)象
單例模式分為餓漢模式和懶漢模式
??1. 餓漢模式
餓漢模式是在類加載的時(shí)候就創(chuàng)建實(shí)例
這種方式是滿足線程安全的(JVM內(nèi)部使用了加鎖,即多個(gè)線程調(diào)用靜態(tài)方法,只有一個(gè)線程競(jìng)爭(zhēng)到鎖并且完成創(chuàng)建,只執(zhí)行一次)
實(shí)現(xiàn)代碼:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } }
??2. 懶漢模式
懶漢模式是在類加載的時(shí)候不創(chuàng)建實(shí)例,第一次使用的時(shí)候才創(chuàng)建
實(shí)現(xiàn)代碼:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
觀察上述代碼,在單線程下不存在線程安全問(wèn)題,但是在多線程環(huán)境下存在安全問(wèn)題嗎?
分析:
??當(dāng)實(shí)例沒(méi)有被創(chuàng)建的時(shí)候,如果有多個(gè)線程都調(diào)用getInstance方法,就可能創(chuàng)建多個(gè)實(shí)例,就存在線程安全問(wèn)題
??但是實(shí)例一旦創(chuàng)建好,后面線程調(diào)用getInstance方法就不會(huì)出現(xiàn)線程安全問(wèn)題結(jié)果:線程安全問(wèn)題出現(xiàn)在首次創(chuàng)建實(shí)例的時(shí)候
??3. 懶漢模式(使用synchronized改進(jìn))
我們使用sychronized修飾,?????代碼如下:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
這樣實(shí)現(xiàn)線程安全存在什么問(wèn)題呢?
解析:
我們對(duì)方法使用synchronized修飾,也就是每次調(diào)用該方法的時(shí)候都會(huì)競(jìng)爭(zhēng)鎖,但是創(chuàng)建實(shí)例只需要?jiǎng)?chuàng)建一次,也就是創(chuàng)建實(shí)例后,再調(diào)用該方法還需要競(jìng)爭(zhēng)鎖釋放鎖結(jié)果:雖然滿足線程安全,但是效率低
4. 懶漢模式(使用雙重校驗(yàn)鎖改進(jìn))
在上述代碼的基礎(chǔ)上進(jìn)行改動(dòng):
使用雙重if判定,降低競(jìng)爭(zhēng)鎖頻率
使用volatile修飾instance
實(shí)現(xiàn)代碼:
public class Singleton { private static volatile Singleton instance = null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance == null){ //外層的if判斷:如果實(shí)例被創(chuàng)建直接return,不讓線程再繼續(xù)競(jìng)爭(zhēng)鎖 //在沒(méi)有創(chuàng)建實(shí)例時(shí),多個(gè)線程已經(jīng)進(jìn)入if判斷了 //一個(gè)線程競(jìng)爭(zhēng)到鎖,其他線程阻塞等待 synchronized (Singleton.class) { //內(nèi)層的if判斷,目的是讓競(jìng)爭(zhēng)失敗的鎖如果再次競(jìng)爭(zhēng)成功的話判斷實(shí)例是否被創(chuàng)建,創(chuàng)建釋放鎖return,沒(méi)有則創(chuàng)建 if(instance == null){ instance = new Singleton(); } } } return instance; } }
??對(duì)雙重if的解析:
??外層的if判斷:實(shí)例只是被創(chuàng)建一次,當(dāng)實(shí)例已經(jīng)被創(chuàng)建好了就不要后續(xù)操作,直接return返回
??內(nèi)層的if判斷:實(shí)例未被創(chuàng)建時(shí),多個(gè)線程同時(shí)競(jìng)爭(zhēng)鎖,只有一個(gè)線程競(jìng)爭(zhēng)成功并創(chuàng)建實(shí)例,其他競(jìng)爭(zhēng)失敗的線程就會(huì)阻塞等待,當(dāng)?shù)谝痪€程釋放鎖后,這些競(jìng)爭(zhēng)失敗的線程就會(huì)繼續(xù)競(jìng)爭(zhēng),但是實(shí)例已經(jīng)創(chuàng)建好了,所以需要再次進(jìn)行if判斷
畫圖分析,如下所示:
三. volatile的原理
volatile保證了可見(jiàn)性,有序性,在Java層面看,volatile是無(wú)鎖操作,多個(gè)線程對(duì)volatile修飾的變量進(jìn)行讀可以并發(fā)并行執(zhí)行,和無(wú)鎖執(zhí)行效率差不多
volatile修飾的變量中,CPU使用了緩存一致性協(xié)議來(lái)保證讀取的都是最新的主存數(shù)據(jù)
緩存一致性:如果有別的線程修改了volatile修飾的變量,就會(huì)把CPU緩存中的變量置為無(wú)效,要操作這個(gè)變量就要從主存中重新讀取
??四. volatile的擴(kuò)展問(wèn)題(了解)
??如果說(shuō)volatile不保證有序性,雙重校驗(yàn)鎖的寫法是否有問(wèn)題?
關(guān)于new對(duì)象按順序分為3條指令:
??(1) 分配對(duì)象的內(nèi)存空間
??(2) 實(shí)例化對(duì)象
??(3) 賦值給變量
正常的執(zhí)行順序?yàn)?1)(2)(3),JVM可能會(huì)優(yōu)化進(jìn)行重排序后的順序?yàn)?1)(3)(2)
這個(gè)重排序的結(jié)果可能導(dǎo)致分配內(nèi)存空間后,對(duì)象還沒(méi)有實(shí)例化完成,就完成了賦值
在這個(gè)錯(cuò)誤的賦值后,instance==null不成立,線程就會(huì)拿著未完成實(shí)例化的instance,使用它的屬性和方法就會(huì)出錯(cuò)
使用volatile保證有序性后:
線程在new對(duì)象時(shí)不管(1)(2)(3)是什么順序,后續(xù)線程拿到的instance是已經(jīng)實(shí)例化完成的
CPU里邊,基于volatile變量操作是有CPU級(jí)別的加鎖機(jī)制(它保證(1)(2)(3)全部執(zhí)行完,寫回主存,再執(zhí)行其他線程對(duì)該變量的操作)
到此這篇關(guān)于Java單例模式中的線程安全問(wèn)題的文章就介紹到這了,更多相關(guān)Java單例模式線程安全內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java判定一個(gè)數(shù)值是否在指定的開閉區(qū)間范圍內(nèi)
這篇文章主要給大家介紹了關(guān)于使用Java判定一個(gè)數(shù)值是否在指定的開閉區(qū)間范圍內(nèi)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-09-09SpringBoot項(xiàng)目部署到阿里云服務(wù)器的實(shí)現(xiàn)步驟
本文主要介紹了SpringBoot項(xiàng)目部署到阿里云服務(wù)器的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06httpclient 請(qǐng)求http數(shù)據(jù),json轉(zhuǎn)map的實(shí)例
下面小編就為大家?guī)?lái)一篇httpclient 請(qǐng)求http數(shù)據(jù),json轉(zhuǎn)map的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12Spring如何替換掉默認(rèn)common-logging.jar
這篇文章主要介紹了Spring如何替換掉默認(rèn)common-logging.jar,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Java8使用stream實(shí)現(xiàn)list中對(duì)象屬性的合并(去重并求和)
這篇文章主要介紹了Java8使用stream實(shí)現(xiàn)list中對(duì)象屬性的合并(去重并求和),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01解決CollectionUtils.isNotEmpty()不存在的問(wèn)題
這篇文章主要介紹了解決CollectionUtils.isNotEmpty()不存在的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02MyBatis學(xué)習(xí)教程(三)-MyBatis配置優(yōu)化
這篇文章主要介紹了MyBatis學(xué)習(xí)教程(三)-MyBatis配置優(yōu)化的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-05-05