<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本篇文章將介紹Java多執行緒中的幾個典型案例之單例模式,所謂單例模式,就是一個類只有一個範例物件,本文將著重介紹在多執行緒的背景下,單例模式的簡單實現。
單例模式,是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個範例,即一個類只有一個物件範例。
單例模式有兩種典型的實現,一是餓漢模式,二是懶漢模式,餓漢模式中的“餓”並不是真的表示“餓”,更加準確的來說應該是表示“急”,那就是一個單例類還沒被使用,它的單例物件就已經建立好了,而懶漢模式,要等到使用這個單例類時才建立單例物件。
單例模式中的單例類,只能擁有一個範例物件,又static
修飾的成員是屬於類的,也就是隻有一份,所以我們可以使用static
修飾的成員變數儲存範例物件的參照。
由於單例模式中,一個類只能擁有一個範例物件,所以需要將類構造方法封裝,防止類被建立多個範例物件,但是在使用該類時必須要得到該類的範例物件,因此我們得建立一個獲取該唯一範例物件的方法getInstance
。
而對於該類的範例物件,在類中我們可以使用屬於類的成員變數來儲存(即static
成員變數)。
//單例模式 - 餓漢模式 class HungrySingleton { //1.使用一個變數來儲存該類唯一的範例,因為單例模式在一個程式中只能擁有一個範例,由於static成員只有一份,我們可以使用static變數來儲存 private static final HungrySingleton instance = new HungrySingleton(); //2.封裝構造方法,防止該類被範例出新的物件 private HungrySingleton() {} //3.獲取該類的唯一範例物件 public HungrySingleton getInstance() { return instance; } }
多執行緒情況下,對於上述簡單實現的餓漢式單例模式,只需要考慮getInstance
方法是否執行緒安全即可,由於該方法就一句返回語句,即一次讀操作,而讀操作是執行緒安全的,所以getInstance
方法也就是執行緒安全的,綜上餓漢式單例模式是執行緒安全的。
懶漢模式相比於餓漢模式,區別就是範例物件建立時機不同,懶漢模式需要等到第一次使用時才建立範例物件,所以僅僅只需要修改獲取物件的方法即可。
不考慮多執行緒情況,懶漢模式實現程式碼如下:
//單例模式 - 懶漢模式 class SlackerSingleton { //1.使用一個變數來儲存該類唯一的範例,因為單例模式在一個程式中只能擁有一個範例,由於static成員只有一份,我們可以使用static變數來儲存 //懶漢單例模式是在使用的時候建立物件,因此初始時物件不應該被建立 private static SlackerSingleton instance; //2.封裝構造方法,防止該類被範例出新的物件 private SlackerSingleton() {} //3.獲取該類的唯一物件,如果沒有就建立 public SlackerSingleton getInstance() { if (instance == null) { instance = new SlackerSingleton(); } return instance; } }
多執行緒情況下,由於getInstance
方法中存在兩次讀(一次判斷一次返回)操作一次寫操作(修改intsance
變數的值),instance
變數為初始化時(即instance=null
)可能會存在多個執行緒進入判斷語句,這樣該類可能會被範例出多個物件,所以上述實現的懶漢式單例模式是執行緒不安全的。
造成執行緒不安全的程式碼段為if語句裡面的讀操作和instance
的修改操作,所以我們需要對這段程式碼進行加鎖,然後就得到了執行緒安全的懶漢模式:
//多執行緒情況下餓漢模式獲取物件時唯讀不修改,所以是執行緒安全的 //多執行緒情況下懶漢模式獲取物件時存在兩次讀操作,分別為判斷instance是否為null和返回instance,除了讀操作還存在修改操作,即新建物件並使instance指向該物件 //懶漢模式物件還未初始化的時候,可能會存在多個執行緒進入判斷語句,會導致範例出多個物件,因此懶漢單例模式是執行緒不安全的。 //執行緒安全單例模式 - 懶漢模式 class SafeSlackerSingleton { //1.使用一個變數來儲存該類唯一的範例,因為單例模式在一個程式中只能擁有一個範例,由於static成員只有一份,我們可以使用static變數來儲存 //懶漢單例模式是在使用的時候建立物件,因此初始時物件不應該被建立 private static SafeSlackerSingleton instance; //2.封裝構造方法,防止該類被範例出新的物件 private SafeSlackerSingleton() {} //3.獲取該類的唯一物件,如果沒有就建立 public SafeSlackerSingleton getInstance() { synchronized (SafeSlackerSingleton.class) { if (instance == null) { instance = new SafeSlackerSingleton(); } } return instance; } }
但是!上述執行緒安全問題只出現在instance
沒有初始化的時候,如果instance
已經初始化了,那個判斷語句就是個擺設,就和餓漢模式一樣,就是執行緒安全的了,如果按照上面的程式碼處理執行緒安全問題,不論instance
是否已經初始化,都要進行加鎖,因此會使鎖競爭加劇,消耗沒有必要消耗的資源,所以在加鎖前需要先判斷一下instance
是否已經初始化,如果為初始化就進行加鎖。
按照上述方案得到以下關於獲取物件的方法程式碼:
public SafeSlackerSingletonPlus getInstance() { //判斷instance是否初始化,如果已經初始化了,那麼該方法只有兩個讀操作,本身就是執行緒安全的,不需要加鎖了,這樣能減少鎖競爭,提高效率 if (instance == null) { synchronized (SafeSlackerSingletonPlus.class) { if (instance == null) { instance = new SafeSlackerSingletonPlus(); } } } return instance; }
到這裡執行緒安全的問題是解決了,但是別忘了編譯器它是不信任你的,它會對你寫的程式碼進行優化! 上面所寫的程式碼需要判斷instance==null
,而多執行緒情況下,很可能頻繁進行判斷,這時候執行緒不會去讀記憶體中的資料,而會直接去暫存器讀資料,這時候instance
值變化時,執行緒完全感知不到!造成記憶體可見性問題,為了解決該問題需要使用關鍵字volatile
修飾instance
變數,防止編譯器優化,從而保證記憶體可見性。
//執行緒安全優化單例模式 - 懶漢模式 class SafeSlackerSingletonPlus { //1.使用一個變數來儲存該類唯一的範例,因為單例模式在一個程式中只能擁有一個範例,由於static成員只有一份,我們可以使用static變數來儲存 //懶漢單例模式是在使用的時候建立物件,因此初始時物件不應該被建立 private static volatile SafeSlackerSingletonPlus instance; //2.封裝構造方法,防止該類被範例出新的物件 private SafeSlackerSingletonPlus() {} //3.獲取該類的唯一物件,如果沒有就建立 public SafeSlackerSingletonPlus getInstance() { //判斷instance是否初始化,如果已經初始化了,那麼該方法只有兩個讀操作,本身就是執行緒安全的,不需要加鎖了,這樣能減少鎖競爭,提高效率 //如果執行緒很多,頻繁進行外層或內層if判斷,可能會引發內層可見性問題,因此要給instan變數加上volatile if (instance == null) { synchronized (SafeSlackerSingletonPlus.class) { if (instance == null) { instance = new SafeSlackerSingletonPlus(); } } } return instance; } }
除了使用餓漢和懶漢模式還可以使用列舉的方式實現,在《Effective Java》書中有這樣一句話:單元素的列舉型別已經成為實現Singleton的最佳方法。 因為列舉就是一個天然的單例,並且列舉型別通過反射都無法獲取封裝的私有變數,非常安全。
//單元素的列舉型別已經成為實現Singleton的最佳方法 enum EnumSingleton { INSTANCE; public void doSomething() { System.out.println("完成一些任務!"); } }
使用方式:
public class Singleton { public static void main(String[] args) { EnumSingleton.INSTANCE.doSomething(); } }
執行結果:
好了,有關多執行緒單例模式問題就討論到這裡了,你學會了嗎?
到此這篇關於Java多執行緒案例之單例模式懶漢+餓漢+列舉的文章就介紹到這了,更多相關Java單例模式內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45