單態定義:
Singleton模式主要作用是保證在Java應用程序中,一個類Class只有一個實例存在。
Singleton模式就為我們提供了這樣實現的可能。使用Singleton的好處還在於可以節省內存,因為它限制了實例的個數,有利於Java垃圾回收(garbage collection)。
使用Singleton注意事項:
有時在某些情況下,使用Singleton並不能達到Singleton的目的,如有多個Singleton對象同時被不同的類裝入器裝載;在EJB這樣的分布式系統中使用也要注意這種情況,因為EJB是跨服務器,跨JVM的
單態模式的演化:
單態模式是個簡單的模式,但是這個簡單的模式也有很多復雜的東西。
(注意:在這裡補充一下,現在單態模式其實有一個寫法是不錯的見這裡:http://www.blogJava.Net/dreamstone /archive/2007/02/27/101000.Html,但還是建議看完這篇文章,因為解釋的事情是不一樣的,這裡說的是為什麼double- checked不能使用。)
一,首先最簡單的單態模式,單態模式1
import Java.util.*;
class Singleton
{
private static Singleton instance;
private Vector v;
private boolean inUse;
private Singleton()
{
v = new Vector();
v.addElement(new Object());
inUse = true;
}
public static Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}
這個單態模式是不安全的,為什麼說呢 ?因為沒考慮多線程,如下情況
Thread 1 調用getInstance() 方法,並且判斷instance是null,然後進入if模塊,
在實例化instance之前,
Thread 2搶占了Thread 1的cpu
Thread 2 調用getInstance() 方法,並且判斷instance是null,然後進入if模塊,
Thread 2 實例化instance 完成,返回
Thread 1 再次實例化instance
這個單態已經不在是單態
二,為了解決剛才的問題:單態模式2
public static synchronized Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
采用同步來解決,這種方式解決了問題,但是仔細分析正常的情況下只有第一次時候,進入對象的實例化,須要同步,其它時候都是直接返回已經實例化好的instance不須要同步,大家都知到在一個多線程的程序中,如果同步的消耗是很大的,很容易造成瓶頸
三,為了解決上邊的問題:單態模式3,加入同步
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
同步改成塊同步,而不使用函數同步,但是仔細分析,
又回到了模式一的狀態,再多線程的時候根本沒有解決問題
四,為了對應上邊的問題:單態模式4,也就是很多人采用的Double-checked locking
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
這樣,模式一中提到的問題解決了。不會出現多次實例化的現象
當第一次進入的時候,保正實例化時候的單態,在實例化後,多線程訪問的時候直接返回,不須要進入同步模塊,既實現了單態,又沒有損失性能。表面上看我們的問題解決了,但是再仔細分析:
我們來假象這中情況:
Thread 1 :進入到//3位置,執行new Singleton(),但是在構造函數剛剛開始的時候被Thread2搶占cpu
Thread 2 :進入getInstance(),判斷instance不等於null,返回instance,
(instance已經被new,已經分配了內存空間,但是沒有初始化數據)
Thread 2 :利用返回的instance做某些操做,失敗或者異常
Thread 1 :取得cpu初始化完成
過程中可能有多個線程取到了沒有完成的實例,並用這個實例作出某些操做。
-----------------------------------------
出現以上的問題是因為
mem = allocate(); //分配內存
instance = mem; //標記instance非空
//未執行構造函數,thread 2從這裡進入
ctorSingleton(instance); //執行構造函數
//返回instance
五,證明上邊的假想是可能發生的,字節碼是用來分析問題的最好的工具,可以利用它來分析下邊一段程序:(為了分析方便,所以漸少了內容)
字節碼的使用方法見這裡,利用字節碼分析問題
class Singleton
{
private static Singleton instance;
private boolean inUse;
private int val;
private Singleton()
{
inUse = true;
val = 5;
}
public static Singleton getInstance()
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
得到的字節碼
;asm code generated for getInstance
054D20B0 mov eax,[049388C8] ;load instance ref
054D20B5 test eax,eax ;test for null
054D20B7 jne 054D20D7
054D20B9 mov eax,14C0988h
054D20BE call 503EF8F0 ;allocate memory
054D20C3 mov [049388C8],eax ;store pointer in
;instance ref. instance
;non-null and ctor
;has not run
054D20C8 mov ecx,dWord ptr [eax]
054D20CA mov dWord ptr [ecx],1 ;inline ctor - inUse=true;
054D20D0 mov dWord ptr [ecx+4],5 ;inline ctor - val=5;
054D20D7 mov ebx,dWord ptr ds:[49388C8h]
054D20DD jmp 054D20B0
上邊的字節碼證明,猜想是有可能實現的
六:好了,上邊證明Double-checked locking可能出現取出錯誤數據的情況,那麼我們還是可以解決的
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}
利用Double-checked locking 兩次同步,中間變量,解決上邊的問題。
(下邊這段話我只能簡單的理解,翻譯過來不好,所以保留原文,list 7是上邊的代碼,list 8是下邊的
The code in Listing 7 doesn‘t work because of the current definition of the memory model.
The Java Language Specification (JLS) demands that code within a synchronized block
not be moved out of a synchronized block. However, it does not say that
code not in a synchronized block cannot be moved into a synchronized block.
A JIT compiler would see an optimization opportunity here.
This optimization would remove the code at
//4 and the code at //5, combine it and generate the code shown in Listing 8:)
-------------------------------------------------
list 8
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
//inst = new Singleton(); //4
instance = new Singleton();
}
//instance = inst; //5
}
}
}
return instance;
}