常見單例模式
1.餓漢式
/**
* 缺點:有現(xiàn)場創(chuàng)建資源,如果沒用到造成內(nèi)存浪費
*/
public class HungrySingleton implements Serializable {
/**
* 私有化構(gòu)造方法
*/
private HungrySingleton() {
}
/**
* 或者 靜態(tài)代碼塊初始化
* private static final HungrySingleton HUNGRY_SINGLETON;
* static{
* HUNGRY_SINGLETON = new HungrySingleton();
* }
*/
private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();
public static HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
}
測試用例
public class HungrySingletonTest {
/**
* 單例校驗
*/
@Test
public void testSingleton() {
Set obj = new ConcurrentSkipListSet();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
HungrySingleton instance = HungrySingleton.getInstance();
if (obj.add(instance.toString())) {
System.out.println(instance.toString());
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
countDownLatch.countDown();
}
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.懶漢式
public class LazySingleton implements Serializable {
private LazySingleton() {
}
private static LazySingleton LAZY_SINGLETON;
/**
* synchronized 關(guān)鍵字添加鎖,性能下降
* @return
*/
public static synchronized LazySingleton getInstance() {
if (LAZY_SINGLETON == null) {
LAZY_SINGLETON = new LazySingleton();
}
return LAZY_SINGLETON;
}
}
3. Double Check 懶漢式
public class DoubleCheckSingleton {
private DoubleCheckSingleton() {
}
/**
* volatile 關(guān)鍵字防止 JVM指令重排序
*/
private volatile static DoubleCheckSingleton DOUBLECHECK_SINGLETON;
public static DoubleCheckSingleton getInstance() {
/**
* synchronized不加在方法上 確保只在初始化時同步
* 但是如果不用volatile修飾紊服,存在指令重排序問題
* DOUBLECHECK_SINGLETON = new DoubleCheckSingleton();可分解為指令
* memory = allocate(); //1:分配對象的內(nèi)存空間
* ctorInstance(memory); //2:初始化對象
* instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
* JVM指令重排序后可能出現(xiàn)
* memory = allocate(); //1:分配對象的內(nèi)存空間
* instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址,此時對象還沒有被初始化叁幢!
* ctorInstance(memory); //2:初始化對象
*
*/
if (DOUBLECHECK_SINGLETON == null) {
synchronized (DoubleCheckSingleton.class) {
if (DOUBLECHECK_SINGLETON == null) {
DOUBLECHECK_SINGLETON = new DoubleCheckSingleton();
}
}
}
return DOUBLECHECK_SINGLETON;
}
}
4.靜態(tài)內(nèi)部類
public class InnerClassSingleton {
private InnerClassSingleton() {
}
/**
* 調(diào)用InnercClassHolder才創(chuàng)建實例
*
* @return
*/
public static InnerClassSingleton getInstance() {
return InnerClassHolder.LAZY;
}
private static class InnerClassHolder {
private static final InnerClassSingleton LAZY = new InnerClassSingleton();
}
}
5.枚舉實現(xiàn)注冊式
Effective Java 推薦實現(xiàn)方式饵沧,枚舉天生不支持反射創(chuàng)建,也不存在序列化破壞單例問題
public enum RegSingleton {
INSTANCE;
public int doSomeing() {
return INSTANCE.hashCode();
}
}
測試用例
@Test
public void testSingleton() {
Set obj = new ConcurrentSkipListSet();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
if (obj.add(RegSingleton.INSTANCE.doSomeing())) {
System.out.println(RegSingleton.INSTANCE.doSomeing());
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
countDownLatch.countDown();
}
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
反射對單例的破壞
/**
* 反射破壞單例
* 解決方案 在私有構(gòu)造方法拋出異常
*/
@Test
public void refSingleton() {
try {
Class<?> clazz = HungrySingleton.class;
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
HungrySingleton instance1 = (HungrySingleton)
declaredConstructor.newInstance();
System.out.println(instance1);
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println(instance2);
System.out.println(instance1 == instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
解決方案
/**
* 解決反射破壞單例
*/
private HungrySingleton() {
if (HUNGRY_SINGLETON != null) {
throw new RuntimeException("不允許反射創(chuàng)建實例");
}
}
序列化對單例的破壞
/**
* 序列化破壞單例
*/
@Test
public void seriable() {
try {
HungrySingleton instance = HungrySingleton.getInstance();
FileOutputStream out = new FileOutputStream("HungrySingleton");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(instance);
out.close();
objectOutputStream.close();
FileInputStream in = new FileInputStream(new File("HungrySingleton"));
ObjectInputStream objectInputStream = new ObjectInputStream(in);
HungrySingleton hungrySingleton = (HungrySingleton)
objectInputStream.readObject();
in.close();
objectInputStream.close();
System.out.println(instance);
System.out.println(hungrySingleton);
System.out.println(instance == hungrySingleton);
} catch (Exception e) {
e.printStackTrace();
}
}
解決方案,JDK在反序列化時會檢測readResolve方法,通過復(fù)寫方法防止新建實例闺金。
/**
* 防止序列化破壞單例
* JDK反序列化時 使用反射調(diào)用無參構(gòu)造函數(shù)創(chuàng)建實例
* @return
*/
public Object readResolve() {
return HUNGRY_SINGLETON;
}