国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發(fā)設(shè)計 > 正文

[轉(zhuǎn)]如何正確地寫出單例模式

2019-11-14 23:18:14
字體:
供稿:網(wǎng)友
[轉(zhuǎn)]如何正確地寫出單例模式

單例模式算是設(shè)計模式中最容易理解,也是最容易手寫代碼的模式了吧。但是其中的坑卻不少,所以也常作為面試題來考。本文主要對幾種單例寫法的整理,并分析其優(yōu)缺點。很多都是一些老生常談的問題,但如果你不知道如何創(chuàng)建一個線程安全的單例,不知道什么是雙檢鎖,那這篇文章可能會幫助到你。

懶漢式,線程不安全

當被問到要實現(xiàn)一個單例模式時,很多人的第一反應(yīng)是寫出如下的代碼,包括教科書上也是這樣教我們的。

 1 public class Singleton { 2     PRivate static Singleton instance; 3  4     private Singleton() { 5     } 6  7     public static Singleton getInstance() { 8         if (instance == null) { 9             instance = new Singleton();10         }11         return instance;12     }13 }
View Code

這段代碼簡單明了,而且使用了懶加載模式,但是卻存在致命的問題。當有多個線程并行調(diào)用 getInstance() 的時候,就會創(chuàng)建多個實例。也就是說在多線程下不能正常工作。

懶漢式,線程安全

為了解決上面的問題,最簡單的方法是將整個 getInstance() 方法設(shè)為同步(synchronized)。

1 public static synchronized Singleton getInstance() {2     if (instance == null) {3         instance = new Singleton();4     }5     return instance;6 }
View Code

雖然做到了線程安全,并且解決了多實例的問題,但是它并不高效。因為在任何時候只能有一個線程調(diào)用 getInstance() 方法。但是同步操作只需要在第一次調(diào)用時才被需要,即第一次創(chuàng)建單例實例對象時。這就引出了雙重檢驗鎖。

雙重檢驗鎖

雙重檢驗鎖模式(double checked locking pattern),是一種使用同步塊加鎖的方法。程序員稱其為雙重檢查鎖,因為會有兩次檢查instance == null,一次是在同步塊外,一次是在同步塊內(nèi)。為什么在同步塊內(nèi)還要再檢驗一次?因為可能會有多個線程一起進入同步塊外的 if,如果在同步塊內(nèi)不進行二次檢驗的話就會生成多個實例了。

 1 public static Singleton getSingleton() { 2     if (instance == null) { // Single Checked 3         synchronized (Singleton.class) { 4             if (instance == null) { // Double Checked 5                 instance = new Singleton(); 6             } 7         } 8     } 9     return instance;10 }
View Code

這段代碼看起來很完美,很可惜,它是有問題。主要在于instance = new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

  1. 給 instance 分配內(nèi)存
  2. 調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量
  3. 將instance對象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)

但是在 JVM 的即時編譯器中存在指令重排序的優(yōu)化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時 instance 已經(jīng)是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。

我們只需要將 instance 變量聲明成 volatile 就可以了。

 1 public class Singleton { 2     private volatile static Singleton instance; // 聲明成 volatile 3  4     private Singleton() { 5     } 6  7     public static Singleton getSingleton() { 8         if (instance == null) { 9             synchronized (Singleton.class) {10                 if (instance == null) {11                     instance = new Singleton();12                 }13             }14         }15         return instance;16     }17 }
View Code

有些人認為使用 volatile 的原因是可見性,也就是可以保證線程在本地不會存有 instance 的副本,每次都是去主內(nèi)存中讀取。但其實是不對的。使用 volatile 的主要原因是其另一個特性:禁止指令重排序優(yōu)化。也就是說,在 volatile 變量的賦值操作后面會有一個內(nèi)存屏障(生成的匯編代碼上),讀操作不會被重排序到內(nèi)存屏障之前。比如上面的例子,取操作必須在執(zhí)行完 1-2-3 之后或者 1-3-2 之后,不存在執(zhí)行到 1-3 然后取到值的情況。從「先行發(fā)生原則」的角度理解的話,就是對于一個 volatile 變量的寫操作都先行發(fā)生于后面對這個變量的讀操作(這里的“后面”是時間上的先后順序)。

但是特別注意在 java 5 以前的版本使用了 volatile 的雙檢鎖還是有問題的。其原因是 Java 5 以前的 JMM (Java 內(nèi)存模型)是存在缺陷的,即時將變量聲明成 volatile 也不能完全避免重排序,主要是 volatile 變量前后的代碼仍然存在重排序問題。這個 volatile 屏蔽重排序的問題在 Java 5 中才得以修復,所以在這之后才可以放心使用 volatile。

相信你不會喜歡這種復雜又隱含問題的方式,當然我們有更好的實現(xiàn)線程安全的單例模式的辦法。

餓漢式 static final field

這種方法非常簡單,因為單例的實例被聲明成 static 和 final 變量了,在第一次加載類到內(nèi)存中時就會初始化,所以創(chuàng)建實例本身是線程安全的。

 1 public class Singleton { 2     // 類加載時就初始化 3     private static final Singleton instance = new Singleton(); 4  5     private Singleton() { 6     } 7  8     public static Singleton getInstance() { 9         return instance;10     }11 }
View Code

這種寫法如果完美的話,就沒必要在啰嗦那么多雙檢鎖的問題了。缺點是它不是一種懶加載模式(lazy initialization),單例會在加載類后一開始就被初始化,即使客戶端沒有調(diào)用 getInstance()方法。餓漢式的創(chuàng)建方式在一些場景中將無法使用:譬如 Singleton 實例的創(chuàng)建是依賴參數(shù)或者配置文件的,在 getInstance() 之前必須調(diào)用某個方法設(shè)置參數(shù)給它,那樣這種單例寫法就無法使用了。

靜態(tài)內(nèi)部類 static nested class

我比較傾向于使用靜態(tài)內(nèi)部類的方法,這種方法也是《Effective Java》上所推薦的。

 1 public class Singleton { 2     private static class SingletonHolder { 3         private static final Singleton INSTANCE = new Singleton(); 4     } 5  6     private Singleton() { 7     } 8  9     public static final Singleton getInstance() {10         return SingletonHolder.INSTANCE;11     }12 }
View Code

這種寫法仍然使用JVM本身機制保證了線程安全問題;由于 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它,因此它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴 JDK 版本。

枚舉 Enum

用枚舉寫單例實在太簡單了!這也是它最大的優(yōu)點。下面這段代碼就是聲明枚舉實例的通常做法。

1 public enum EasySingleton {2     INSTANCE;3 }
View Code

我們可以通過EasySingleton.INSTANCE來訪問實例,這比調(diào)用getInstance()方法簡單多了。創(chuàng)建枚舉默認就是線程安全的,所以不需要擔心double checked locking,而且還能防止反序列化導致重新創(chuàng)建新的對象。但是還是很少看到有人這樣寫,可能是因為不太熟悉吧。

總結(jié)

一般來說,單例模式有五種寫法:懶漢、餓漢、雙重檢驗鎖、靜態(tài)內(nèi)部類、枚舉。上述所說都是線程安全的實現(xiàn),文章開頭給出的第一種方法不算正確的寫法。

就我個人而言,一般情況下直接使用餓漢式就好了,如果明確要求要懶加載(lazy initialization)會傾向于使用靜態(tài)內(nèi)部類,如果涉及到反序列化創(chuàng)建對象時會試著使用枚舉的方式來實現(xiàn)單例。

Read More
  • Double Checked Locking on Singleton Class in Java
  • Why Enum Singleton are better in Java
  • How to create thread safe Singleton in Java
  • 10 Singleton Pattern Interview questions in Java

感謝有您,讓我成長的更快:em18em18em18(Y8OSM1NHC$]3CFH@G11Q%I(Y8OSM1NHC$]3CFH@G11Q%I(Y8OSM1NHC$]3CFH@G11Q%I

em18如何正確地寫出單例模式(Y8OSM1NHC$]3CFH@G11Q%I


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 文安县| 资阳市| 红原县| 杨浦区| 鹤岗市| 武平县| 辽源市| 大新县| 贵南县| 洮南市| 宁波市| 敦化市| 和龙市| 呼图壁县| 绥棱县| 龙泉市| 屏山县| 昌图县| 泗洪县| 改则县| 舟山市| 钟祥市| 河北区| 邻水| 田东县| 宁阳县| 修武县| 昭苏县| 彩票| 航空| 永兴县| 马鞍山市| 滦平县| 德钦县| 桐梓县| 仁布县| 湖北省| 西林县| 峨眉山市| 大丰市| 南靖县|