首先明確一點,單例模式的主旨在於一個類只有一個對象
1、簡單的單例模式
先寫一種最基本的:
public class SingleEasy {
private static SingleEasy singleEasy;
public SingleEasy(){
System.out.println("創建了SingletonDemo...");
}
private static SingleEasy getInstance(){
if (singleEasy == null){
singleEasy = new SingleEasy();
}
return singleEasy;
}
}
這個很好理解,singleEasy == null時才才創建一個對象,否則直接返回已經創建好的對象。
雖然這種方式看起來完成了單例,但是不安全,或者說他只是在單線程的情況下沒問題。原因:
寫個測試類:
public class SingleTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
SingleEasy.getInstance();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
SingleEasy.getInstance();
}
});
thread1.start();
thread2.start();
}
}
執行結果會出現這種情況:
因爲兩個線程併發,導致的錯誤。
2.多線程模式下的單例模式
由於上面的錯誤,所以我們要給getInstance方法加synchronized,保證同一時刻只有一個線程操作此方法。
public class SingleEasy {
private static SingleEasy singleEasy;
private SingleEasy(){
System.out.println("創建了SingletonDemo...");
}
public static synchronized SingleEasy getInstance(){
if (singleEasy == null){
singleEasy = new SingleEasy();
}
return singleEasy;
}
}
面試中單例模式寫到這種程度應該有個七八十分的樣子。
3.雙重檢測單例模式
2中的單例基礎上基礎完善。
2中的synchronized是加在方法上的,所以鎖定的是整個方法,假如方法中的還有其他的代碼需要執行,其實就是影響效率了。所以我們只需要鎖住多線程共享的資源就可以。
先上代碼:
public class SingleEasy {
private static volatile SingleEasy singleEasy;
private SingleEasy(){
System.out.println("創建了SingletonDemo...");
}
public static SingleEasy getInstance(){
if (singleEasy == null){
synchronized(SingleEasy.class){
if (singleEasy == null){
singleEasy = new SingleEasy();
}
}
}
return singleEasy;
}
}
爲了更加保險,做了兩次判斷。這麼寫就算比較完善的單例了。值得注意的是volatile關鍵字。因爲即使我們現在線程安全但人有可能創建兩個對象,因爲jvm內存模型的關係。用了volatile關鍵字就可以讓內存內容對線程直接可見,略過工作內存。