使用場景:
一個類只能創建一個實例對象。如讀取服務器配置文件的類,由單個實例對象直接讀取。
實現流程:
(1)、定義一個私有變量;
(2)、將構造函數私有化;
(3)、提供一個獲取實例的公用方法;
下面列出幾種常見的實現模式,僅供參考:
餓漢式單例:在類創建的時候就初始化實例對象,每次調用的時候都是獲取同一對象實例,是線程安全的。
實現類:
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return instance;
}
}
測試類:
public class HungrySingletonTest {
private class MyThread extends Thread{
@Override
public void run(){
System.out.println(HungrySingleton.getInstance().hashCode());
}
}
public static void main(String[] args) {
MyThread[] mts = new MyThread[10];
for(int i = 0 ; i < mts.length ; i++){
mts[i] = new HungrySingletonTest().new MyThread();
}
for (int j = 0; j < mts.length; j++) {
mts[j].start();
}
}
}
測試結果:
從測試結果可以看出,該方式是線程安全的。
普通懶漢式單例:創建類的時候不實例化對象,在調用獲取實例方法的時候才創建對象。是線程不安全的。
實現類:
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){};
public static LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
測試類:
public class LazySingletonTest {
private class MyThread extends Thread{
@Override
public void run(){
System.out.println(LazySingleton.getInstance().hashCode());
}
}
public static void main(String[] args) {
MyThread[] mts = new MyThread[10];
for(int i = 0 ; i < mts.length ; i++){
mts[i] = new LazySingletonTest().new MyThread();
}
for (int j = 0; j < mts.length; j++) {
mts[j].start();
}
}
}
測試結果:
從測試結果可以看出,該方式是線程不安全的。
對線程不安全的懶漢式單例模式,有多種的解決方案。簡單的如:在獲取實例的公共方法里加上同步標識符synchronized。這樣的話可以保證在多線程的環境下獲取的實例是唯一的,但也相應地影響了效率。這裏列舉一種常規的解決方案——Double Check Locking雙檢查鎖機制。
雙檢查鎖機制(DCL)
實現類:
public class DclSingleton {
//使用volatile保證線程間可見性
private static volatile DclSingleton instance = null;
private DclSingleton(){};
public static DclSingleton getInstance(){
if(instance == null){
synchronized(DclSingleton.class){
//二次檢查
if(instance == null){
instance = new DclSingleton();
}
}
}
return instance;
}
}
從測試結果可以看出,該方式是線程安全的,且將同步的範圍限制到了最小。是推薦的一種單例實現方式。