您好,登錄后才能下訂單哦!
這篇文章主要介紹了怎么解決Java單例模式中的線程安全問題的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇怎么解決Java單例模式中的線程安全問題文章都會有所收獲,下面我們一起來看看吧。
提高效率:使用多線程就是為了充分利用CPU資源,提高任務的效率
線程安全:使用多線程最基本的就是保障線程安全問題
所以我們在設計多線程代碼的時候就必須在滿足線程安全的前提下盡可能的提高任務執行的效
故:
加鎖細粒度化:加鎖的代碼少一點,讓其他代碼可以并發并行的執行
考慮線程安全:
沒有操作共享變量的代碼沒有安全問題
對共享變量的讀,使用volatile修飾變量即可
對共享變量的寫,使用synchronized加鎖
單例模式能保證某個類在程序中只存在唯一一份實例,而不會創建出多個實例
例如:DataSource(數據連接池),一個數據庫只需要一個連接池對象
單例模式分為餓漢模式和懶漢模式
餓漢模式是在類加載的時候就創建實例
這種方式是滿足線程安全的(JVM內部使用了加鎖,即多個線程調用靜態方法,只有一個線程競爭到鎖并且完成創建,只執行一次)
實現代碼:
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; } }
懶漢模式是在類加載的時候不創建實例,第一次使用的時候才創建
實現代碼:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
觀察上述代碼,在單線程下不存在線程安全問題,但是在多線程環境下存在安全問題嗎?
分析:
當實例沒有被創建的時候,如果有多個線程都調用getInstance方法,就可能創建多個實例,就存在線程安全問題
但是實例一旦創建好,后面線程調用getInstance方法就不會出現線程安全問題結果:線程安全問題出現在首次創建實例的時候
我們使用sychronized修飾,????‍?????代碼如下:
public class Singleton { private static Singleton instance = null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
這樣實現線程安全存在什么問題呢?
解析:
我們對方法使用synchronized修飾,也就是每次調用該方法的時候都會競爭鎖,但是創建實例只需要創建一次,也就是創建實例后,再調用該方法還需要競爭鎖釋放鎖結果:雖然滿足線程安全,但是效率低
在上述代碼的基礎上進行改動:
使用雙重if判定,降低競爭鎖頻率
使用volatile修飾instance
實現代碼:
public class Singleton { private static volatile Singleton instance = null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance == null){ //外層的if判斷:如果實例被創建直接return,不讓線程再繼續競爭鎖 //在沒有創建實例時,多個線程已經進入if判斷了 //一個線程競爭到鎖,其他線程阻塞等待 synchronized (Singleton.class) { //內層的if判斷,目的是讓競爭失敗的鎖如果再次競爭成功的話判斷實例是否被創建,創建釋放鎖return,沒有則創建 if(instance == null){ instance = new Singleton(); } } } return instance; } }
對雙重if的解析:
外層的if判斷:實例只是被創建一次,當實例已經被創建好了就不要后續操作,直接return返回
內層的if判斷:實例未被創建時,多個線程同時競爭鎖,只有一個線程競爭成功并創建實例,其他競爭失敗的線程就會阻塞等待,當第一線程釋放鎖后,這些競爭失敗的線程就會繼續競爭,但是實例已經創建好了,所以需要再次進行if判斷
畫圖分析,如下所示:
volatile保證了可見性,有序性,在Java層面看,volatile是無鎖操作,多個線程對volatile修飾的變量進行讀可以并發并行執行,和無鎖執行效率差不多
volatile修飾的變量中,CPU使用了緩存一致性協議來保證讀取的都是最新的主存數據
緩存一致性:如果有別的線程修改了volatile修飾的變量,就會把CPU緩存中的變量置為無效,要操作這個變量就要從主存中重新讀取
如果說volatile不保證有序性,雙重校驗鎖的寫法是否有問題?
關于new對象按順序分為3條指令:
(1) 分配對象的內存空間
(2) 實例化對象
(3) 賦值給變量
正常的執行順序為(1)(2)(3),JVM可能會優化進行重排序后的順序為(1)(3)(2)
這個重排序的結果可能導致分配內存空間后,對象還沒有實例化完成,就完成了賦值
在這個錯誤的賦值后,instance==null不成立,線程就會拿著未完成實例化的instance,使用它的屬性和方法就會出錯
使用volatile保證有序性后:
線程在new對象時不管(1)(2)(3)是什么順序,后續線程拿到的instance是已經實例化完成的
CPU里邊,基于volatile變量操作是有CPU級別的加鎖機制(它保證(1)(2)(3)全部執行完,寫回主存,再執行其他線程對該變量的操作)
關于“怎么解決Java單例模式中的線程安全問題”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“怎么解決Java單例模式中的線程安全問題”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。