這篇文章給大家介紹深入淺析Java設計模式中的單例模式,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
單例模式是非常常見的設計模式,其含義也很簡單,一個類給外部提供一個唯一的實例。下文所有的代碼均在github
源碼整個項目不僅僅有設計模式,還有其他JavaSE知識點,歡迎Star,Fork
單例模式的UML圖

單例模式的關鍵點
通過上面的UML圖,我們可以看出單例模式的特點如下:
1、構造器是私有的,不允許外部的類調用構造器
2、提供一個供外部訪問的方法,該方法返回單例類的實例
如何實現單例模式
上面已經給出了單例模式的關鍵點,我們的實現只需要滿足上面2點即可。但是正因為單例模式的實現方式比較寬松,所以不同的實現方式會有不同的問題。我們可以對單例模式的實現做一下分類,看一看有哪些不同的實現方式。
1根據單例對象的創建時機不同,可以分為餓漢模式和懶漢模式。餓漢是指在類加載的時候,就創建了對象。但是創建對象有時比較消耗資源,會造成類加載很慢,但是優點是獲取對象的速度很快,因為早已經創建好了嘛。懶漢就是相對餓漢而言,在需要返回單例對象的時候,在創建對象,類加載的時候,并不初始化,好處與缺點也不言而喻
2.根據是否實現線程安全,可以分為普通的懶漢模式這種線程不安全的寫法,和餓漢模式,雙重檢查鎖的懶漢模式,以及通過靜態內部類或者枚舉類等實現的線程安全的寫法。
一個線程不安全的單例模式
public class SimpleSingleton {
private static SimpleSingleton simpleSingleton;
private SimpleSingleton(){
}
public static SimpleSingleton getInstance(){
if (simpleSingleton == null) {
simpleSingleton = new SimpleSingleton();
}
return simpleSingleton;
}
}首先,我們可以看出這是一個懶漢模式的實現。因為只有在getInstance的時候,才會真正創建單例的對象。但是為什么他是線程不安全的呢,是因為可能會有2個線程同時進入if (simpleSingleton == null)的判斷,就是同時創建了simpleSingleton對象。
DCL懶漢模式
上面的方法可以看出是存在線程不安全的問題的,我們可以用同步關鍵字synchronized來實現線程安全。我們先逐步分析,先用synchronized來改寫上面的懶漢模式,代碼如下:
public class DCLSingleton {
private static DCLSingleton singleton;
private DCLSingleton(){
}
public synchronized static DClSingleton getSingleton(){
if (singleton == null) {
singleton = new DCLSingleton();
}
return singleton;
}
}這樣,就有效的保證了不會有兩個線程同時執行該方法,但這個效率也太低了吧。因為在創建實例之后,每次得到實例對象,還是需要進行同步,synchronized的同步保證代價是比較大的,因此可以在此基礎上進行改造。在已經創建好之后,就不需要同步了,我們可以改成如下的形式:
public static DCLSingleton getSingleton(){
if (singleton == null) {
synchronized (DCLSingleton.class) {
if (singleton == null) {
singleton = new DCLSingleton();
}
}
}
return singleton;
}其他代碼不變,只看這個方法。該方法的兩重if (singleton == null)可以有效地保證線程安全。比如,當兩個線程同時進入該方法的時候,第一個if,兩者都是進入,下面的代碼,但是碰到同步代碼塊,只能有一個先進入,進入的時候,繼續判斷,再次判斷為空,才會真正創建對象。如果不進行,第二個判斷,那些對于第一個進入的線程而言,確實創建了對象,但是第二個線程,他緊接著也會執行創建對象的操作,因為不知道第一個線程已經創建成功。因此,需要兩次判空。
但是真的就如此簡單的保證了線程安全嗎?我們仔細分析一下這個過程,singleton = new DCLSingleton();這個代碼實際上是3個操作。
1.給DCLSingleton實例分配內存
2.調用DCLSingleton()的構造函數,初始化成員字段
3.將singleton對象指向分配的內存空間。
在JDK1.5以前,上面的3個執行順序是不固定的,有可能是1-2-3,或者1-3-2。如果是1-3-2,則在第一個線程執行完第三步以后,第二個線程立即執行,但還沒有真正的進行初始化,所以就會使用的時候出錯。在JDK1.5以后,我們可以用volatile關鍵字來保證該1-2-3的順序執行。所以,除了getSingleton()方法要改成上面的樣子以外,還需要對private static DCLSingleton singleton; 改寫成private static volatile DCLSingleton singleton; 這樣,就真正保證了線程同步的懶漢寫法的單例模式。
餓漢寫法
餓漢寫法有很多變形,但無論是哪一種變形,都能保證線程安全,因為餓漢寫法是在類加載的時候,就完成了對象的初始化,類加載保證了他們天生是線程安全的。下面給出常見的2中餓漢寫法
public class HungrySingleton {
private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getSingleton(){
return singleton;
}
}
public class HungrySingleton {
private static final HungrySingleton singleton = new HungrySingleton();
private HungrySingleton(){
}
// public static HungrySingleton getSingleton(){
// return singleton;
// }
}這兩種對初始化單例的對象上面,都是一致的, 通過final來保證對象的唯一。不同的是,調用單例對象的方式,第一種是通過getSingleton(),第二種是通過類.類變量的形式。
靜態內部類實現單例模式
雙重檢查鎖(DCL)實現單例模式,雖然解決了線程不安全的問題,以及保證了資源的懶加載,在需要的時候,才會進行實例化的操作。但是在某些情況下(比如JDK低于1.5)會出現DCL失效,所以有一種很簡潔且依舊是懶加載的方法實現單例模式。寫法如下:
public class StaticSingleton {
private StaticSingleton(){
}
public static final StaticSingleton getInstance(){
return Holder.singleton;
}
private static class Holder{
private static final StaticSingleton singleton = new StaticSingleton();
}
}通過靜態內部類的形式,實現單例類的初始化,其特性同樣是通過ClassLoader來保證其單例對象的唯一,但是這是懶加載的,因為只有在Holder類被調用的時候,即getInstance調用的時候,才會加載Holder類從而實現創建對象。
枚舉類實現單例模式
直接看代碼:
public enum EnumSingleton {
SINGLETON;
public void doSometings(){
}
}使用的時候,直接通過EnumSingleton.SINGLETON.doSomethings()。枚舉類天生特性是保證不會有兩個實例,并且只有在第一次訪問的時候才會被實例化,是懶加載的情況。
真的不會再次創建新的對象嗎?
在常規調用單例類的getInstance()方法的情況下,使用線程安全的寫法確實不會創建新的對象,但是Java提供了很多奇特的技巧和使用,下面這些使用會破壞掉常規的單例。
在除了枚舉實現單例模式的方法以外,其余所有方法碰到上述四種情況,都會重新創建對象。原因如下:
什么時候用單例模式,用哪一種寫法的單例模式
單例模式有兩種比較適合的使用場景。
第一種是創建某個對象,需要的代價比較大,為了避免頻繁的創建和銷毀對象從而引起的對資源的浪費,會考慮使用單例模式。
第二種是這個對象必須只有一個,有多個會造成不可預估的錯誤,或者程序的混亂,比如只會有一個序號生成器,一個緩存等等。
針對使用的單例模式,如果需要理解的加載資源,就是用餓漢寫法,在Android應用中,很多對象需要在啟動的時候,立即就使用,比如啟動時,需要拉取相機配置的類管理縮略圖的cache類等等。如果不是立即需要,或者不是貫穿應用始終的,就不需要使用餓漢寫法,可以考慮懶漢寫法用(DCL或者靜態內部類實現)這兩種在一般情況下都不會出現問題。
關于深入淺析Java設計模式中的單例模式就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。