單例模式可以說是最容易理解的模式了,也是應用最廣的模式之一,先看看定義吧。
定義
什麼時候需要使用單例模式呢:如果某個類,創建時需要消耗很多資源,即new出這個類的代價很大;或者是這個類佔用很多內存,如果創建太多這個類實例會導致內存佔用太多。
關於單例模式,雖然很簡單,無需過多的解釋,但是這裏還要提個醒,其實單例模式裏面有很多坑。我們去會會單例模式。最簡單的單例模式如下:
public class TestSingle {
private static TestSingle instance = null;
private TestSingle(){}
public static TestSingle getInstance(){
if (instance== null){
instance = new TestSingle();
}
return instance;
}
}
instance
可能是null
,可能你會想,這有什麼難得,直接在getInstance()
函數上加sychronized
關鍵字不就好了。可是你想過沒有,每次調用getInstance()
時都要執行同步,這帶來沒必要的性能上的消耗。注意,在方法上加sychronized
關鍵字時,一個線程訪問這個方法時,其他線程無法同時訪問這個類其他sychronized
方法。的我們看看另外一種實現:public class TestSingle {
private static TestSingle instance = null;
private TestSingle() {
}
public static TestSingle getInstance() {
if (instance == null) {
synchronized (TestSingle.class) {
if (instance == null) {
instance = new TestSingle();
}
}
}
return instance;
}
}
instance=new Singleton();
這段代碼上。這段代碼會編譯成多條指令,大致上做了3件事:(1)給Singleton實例分配內存
(2)調用Singleton()構造函數,初始化成員字段
(3)將instance對象指向分配的內存(此時instance就不是null啦~)
上面的(2)和(3)的順序無法得到保證的,也就是說,JVM可能先初始化實例字段再把instance
指向具體的內存實例,也可能先把instance
指向內存實例再對實例進行初始化成員字段。考慮這種情況:一開始,第一個線程執行instance=new Singleton();
這句時,JVM先指向一個堆地址,而此時,又來了一個線程2,它發現instance
不是null
,就直接拿去用了,但是堆裏面對單例對象的初始化並沒有完成,最終出現錯誤~
。
看看另外一種方式:
public class TestSingle {
private volatile static TestSingle instance = null;
private TestSingle() {
}
public static TestSingle getInstance() {
if (instance == null) {
synchronized (TestSingle.class) {
if (instance == null) {
instance = new TestSingle();
}
}
}
return instance;
}
}
instance
變量加了一個volatile
關鍵字volatile
關鍵字的作用是:線程每次使用到被volatile
關鍵字修飾的變量時,都會去堆裏拿最新的數據。換句話說,就是每次使用instance時,保證了instance是最新的。注意:volatile
關鍵字並不能解決併發的問題,關於volatile
請查看其它相關文章。但是volatile
能解決我們這裏的問題。