閱讀834 返回首頁    go 阿裏雲 go 技術社區[雲棲]


深入淺出單實例Singleton設計模式

長時間沒有用java單實例模式,現在想起來有點忘了,發些東西大家一起熟悉下。
單實例Singleton設計模式可能是被討論和使用的最廣泛的一個設計模式了,這可能也是麵試中問得最多的一個設計模式了。這個設計模式主要目的是想在整個係統中隻能出現一個類的實例。這樣做當然是有必然的,比如你的軟件的全局配置信息,或者是一個Factory,或是一個主控類,等等。你希望這個類在整個係統中隻能出現一個實例。當然,作為一個技術負責人的你,你當然有權利通過使用非技術的手段來達到你的目的。比如:你在團隊內部明文規定,“XX類隻能有一個全局實例,如果某人使用兩次以上,那麼該人將被處於2000元的罰款!”(嗬嗬),你當然有權這麼做。但是如果你的設計的是東西是一個類庫,或是一個需要提供給用戶使用的API,恐怕你的這項規定將會失效。因為,你無權要求別人會那麼做。所以,這就是為什麼,我們希望通過使用技術的手段來達成這樣一個目的的原因。
本文會帶著你深入整個Singleton的世界,當然,我會放棄使用C++語言而改用Java語言,因為使用Java這個語言可能更容易讓我說明一些事情。
Singleton的教學版本
這裏,我將直接給出一個Singleton的簡單實現,因為我相信你已經有這方麵的一些基礎了。我們姑且把這具版本叫做1.0版

// version 1.0
public class Singleton
{
private static final Singleton singleton = null;
private Singleton()
{
}
public static Singleton getInstance()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
}

在上麵的實例中,我想說明下麵幾個Singleton的特點:(下麵這些東西可能是盡人皆知的,沒有什麼新鮮的)
   1. 私有(private)的構造函數,表明這個類是不可能形成實例了。這主要是怕這個類會有多個實例。
   2. 即然這個類是不可能形成實例,那麼,我們需要一個靜態的方式讓其形成實例:getInstance()。注意這個方法是在new自己,因為其可以訪問私有的構造函數,所以他是可以保證實例被創建出來的。
   3. 在getInstance()中,先做判斷是否已形成實例,如果已形成則直接返回,否則創建實例。
   4. 所形成的實例保存在自己類中的私有成員中。
   5. 我們取實例時,隻需要使用Singleton.getInstance()就行了。
當然,如果你覺得知道了上麵這些事情後就學成了,那我給你當頭棒喝一下了,事情遠遠沒有那麼簡單。
Singleton的實際版本
上麵的這個程序存在比較嚴重的問題,因為是全局性的實例,所以,在多線程情況下,所有的全局共享的東西都會變得非常的危險,這個也一樣,在多線程情況下,如果多個線程同時調用getInstance()的話,那麼,可能會有多個進程同時通過 (singleton== null)的條件檢查,於是,多個實例就創建出來,並且很可能造成內存泄露問題。嗯,熟悉多線程的你一定會說——“我們需要線程互斥或同步”,沒錯,我們需要這個事情,於是我們的Singleton升級成1.1版,如下所示:

// version 1.1
public class Singleton
{
private static final Singleton singleton = null;
private Singleton()
{
}
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}



嗯,使用了Java的synchronized方法,看起來不錯哦。應該沒有問題了吧?!錯!這還是有問題!為什麼呢?前麵已經說過,如果有多個線程同時通過(singleton== null)的條件檢查(因為他們並行運行),雖然我們的synchronized方法會幫助我們同步所有的線程,讓我們並行線程變成串行的一個一個去 new,那不還是一樣的嗎?同樣會出現很多實例。嗯,確實如此!看來,還得把那個判斷(singleton== null)條件也同步起來。於是,我們的Singleton再次升級成1.2版本,如下所示:

// version 1.2
public class Singleton
{
private static final Singleton singleton = null;
private Singleton()
{
}
public static Singleton getInstance()
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
return singleton;
}
}

不錯不錯,看似很不錯了。在多線程下應該沒有什麼問題了,不是嗎?的確是這樣的,1.2版的Singleton在多線程下的確沒有問題了,因為我們同步了所有的線程。隻不過嘛……,什麼?!還不行?!是的,還是有點小問題,我們本來隻是想讓new這個操作並行就可以了,現在,隻要是進入 getInstance()的線程都得同步啊,注意,創建對象的動作隻有一次,後麵的動作全是讀取那個成員變量,這些讀取的動作不需要線程同步啊。這樣的作法感覺非常極端啊,為了一個初始化的創建動作,居然讓我們達上了所有的讀操作,嚴重影響後續的性能啊!
還得改!嗯,看來,在線程同步前還得加一個(singleton== null)的條件判斷,如果對象已經創建了,那麼就不需要線程的同步了。OK,下麵是1.3版的Singleton。
// version 1.3
public class Singleton
{
private static final Singleton singleton = null;
private Singleton()
{
}
public static Singleton getInstance()
{
if (singleton == null)
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
}


感覺代碼開始變得有點羅嗦和複雜了,不過,這可能是最不錯的一個版本了,這個版本又叫“雙重檢查”Double-Check。下麵是說明:
   1. 第一個條件是說,如果實例創建了,那就不需要同步了,直接返回就好了。
   2. 不然,我們就開始同步線程。
   3. 第二個條件是說,如果被同步的線程中,有一個線程創建了對象,那麼別的線程就不用再創建了。
相當不錯啊,幹得非常漂亮!請大家為我們的1.3版起立鼓掌!

 

 

本文轉載自:https://www.bangchui.org/read.php?tid=3663

最後更新:2017-04-04 07:03:12

  上一篇:go 通過Java HTTP連接將網絡圖片下載到本地
  下一篇:go 矽穀神童為何總會高估自己