上一篇文章介绍了单例模式的最常见的两种,饿汉式和懒汉式,以及两种单例模式的优缺点以及解决方案,传送门如下:
今天继续研究单例模式的几种高级模式。
首先解决一下上篇文章中的一个问题,解决懒汉式单例线程安全的问题,有两种方式:
1、内部类,代码如下:
package com.rq.pattern;
public class LazyStaticInnerSingten {
private LazyStaticInnerSingten() {}
public static LazyStaticInnerSingten getInstance() {
return LazyHold.INSTANCE;
}
private static class LazyHold{
private static final LazyStaticInnerSingten INSTANCE = new LazyStaticInnerSingten();
}
}
内部类在调用之前就初始化了,所以不存在线程安全问题,由于其加载的特性(在调用时才加载)同时避免了他不会和饿汉式单例一样存在资源浪费的问题,所以完美地解决了线程安全和资源浪费两个问题。但是有另外一个问题,后面说。
2、双重检测机制,代码如下:
public class LazySingten {
private LazySingten() {}
private static LazySingten instance;
public static LazySingten getInstance() {
//检查是否要阻塞,解决线程安全问题
if(instance == null) {
synchronized (LazySingten.class) {
//检查是否要重新创建实例
if(instance == null) {
instance = new LazySingten();
}
}
}
return instance;
}
}
介绍完两种模式,再继续提出问题,内部类的方式虽然很完美,但是又出现一个新的问题,就是它会被反射破坏,代码如下:
public class LazyStaticInnerSingtenTest {
public static void main(String[] args) {
Class<?> clazz = LazyStaticInnerSingten.class;
try {
//获得构造方法
Constructor<?> c = clazz.getDeclaredConstructor(null);
//设置强制访问
c.setAccessible(true);
//生成实例
Object newInstance1 = c.newInstance();
Object newInstance2 = c.newInstance();
System.out.println(newInstance1);
System.out.println(newInstance2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果:
com.rq.pattern.LazySingten@15db9742
com.rq.pattern.LazySingten@6d06d69c
可以看出,两个实例并不相同,也就是说,单例被破坏了,如何解决?方式如下:
1、在构造方法中,判断,如果单例被破坏,直接抛出异常,简单粗暴,代码如下:
public class LazyStaticInnerSingten {
private LazyStaticInnerSingten() {
if(LazyHold.INSTANCE != null) {
throw new RuntimeException("不允许非法访问!");
}
}
public static LazyStaticInnerSingten getInstance() {
return LazyHold.INSTANCE;
}
private static class LazyHold{
private static final LazyStaticInnerSingten INSTANCE = new LazyStaticInnerSingten();
}
}
再测试,执行结果如下:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.rq.pattern.LazyStaticInnerSingtenTest.main(LazyStaticInnerSingtenTest.java:16)
Caused by: java.lang.RuntimeException: 不允许非法访问!
at com.rq.pattern.LazyStaticInnerSingten.<init>(LazyStaticInnerSingten.java:7)
... 5 more
阻止了通过反射的方式破坏单例。
下一篇文章会继续讲解单例模式的另外几种更高级的写法。