單例模式是23種設計模式中比較常見的設計模式,又因為其代碼量精簡,所以經常會被用在在面試中測試面試者的能力。
初級的單例模式很簡單
實現兩個要求
1構造方法私有化
2對外提供靜態的,公開的獲取對象的方法
所以:初級單例模式如下
public class Singelton {
private Singelton(){}
private static Singelton sin=null;
public static Singelton getSingelton(){
if(sin==null){
sin=new Singelton();
}
return sin;
}
}
----------------------------------------
但是這樣就夠了嗎?
隨着學習的深入,我們知道程序大多數在多線程的環境下運行。這就對我們的代碼提出了更高質量的要求,要求我們考慮線程安全問題。
僅僅是上面的那段代碼無法保證線程的安全。於是:
public class SingletonThread {
private SingletonThread() {}
private static SingletonThread st=null;
public synchronized static SingletonThread getSingletonThread(){
if(st==null){
st=new SingletonThread();
}
return st;
}
}
這段代碼考慮到了線程安全,但是,在方法上加鎖代價是否太大了?效率與單線程相近,假設這個方法中有上萬行代碼,在方法上加鎖
是很不划算的。
所以,我們有更好的方法
1
public final class SingletonOne {//餓漢式,不能實現按需分配
private SingletonOne(){};
private static SingletonOne sin=new SingletonOne();
public static SingletonOne getSingleton(){
return sin;
}
}
利用靜態成員僅在類加載階段執行一次的性質,得到唯一的對象。
此方法不僅線程安全,而且方法簡介。
2我們能否不一開始就創建類的實例呢?做到按需分配
如下:
public final class SingletonTwo {
private SingletonTwo(){};
public static SingletonTwo setsin(){
return singleton.sin;
}
static class singleton{//內部類不會再外部類加載時加載,故此是按需分配。
private singleton() {};
private static final SingletonTwo sin=new SingletonTwo();
}
}
利用內部類不會在外部類加載時被加載的性質,真正實現了按需分配。
---------------------------------------------
以上兩種方法是極好的,但是也需要根據實際情況使用,因為類中的方法和屬性都是靜態的,即使被繼承之后也會被隱藏,
不能通過重寫來實現多態,已經失去了被繼承的意義,故此還有另一種推薦方法:
3
public class SingletonV {
private SingletonV(){}
private volatile SingletonV singleton=null;
public SingletonV getSingleton(){
if(singleton==null){
synchronized (SingletonV.class) {
if(singleton==null){
singleton=new SingletonV();
}
}
}
return singleton;
}
}
這樣類還保留了繼承的意義,同樣要加鎖,但是開銷小得多。利用了關鍵字volatile。
具體用法如下
//java內存模型規定所有的變量都是存在主存當中(類似於前面說的物理內存),
//每個線程都有自己的工作內存(類似於前面的高速緩存)。
//線程對變量的所有操作都必須在工作內存中進行,
//而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。
//這就可能造成一個線程在主存中修改了一個變量的值,
//而另外一個線程還繼續使用它在自己工作內存中的變量值的拷貝,造成數據的不一致。
//要解決這個問題,把該變量聲明為volatile(不穩定的)即可,
//這就指示JVM,這個變量是不穩定的, 每次使用它都到 主存中 進行讀取。
//一般說來,多任務環境下各任務間共享的標志都應該加volatile修飾。
//Volatile修飾的成員變量在每次被線程訪問時, 都強迫 從共享內存中 重讀 該成員變量的值。
//而且,當成員變量發生變化時,強迫 線程將變化值 回寫到共享內存。這樣在任何時刻,
//兩個不同的線程總是看到某個成員變量的同一個值。
