在多线程中使用DCL懒汉式,8个线程只有一个线程能使用该类的构造方法,说明DCL懒汉式单例是线程安全的
public class DCLLazyMan {
private DCLLazyMan(){
System.out.println("DCL懒汉式单例 " + Thread.currentThread().getName());
}
private volatile static DCLLazyMan lazyMan;
public static DCLLazyMan getInstance(){
if (lazyMan == null){
synchronized (DCLLazyMan.class){
if (lazyMan == null){
lazyMan = new DCLLazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i <9 ; i++) {
new Thread(()->{
DCLLazyMan.getInstance();
}).start();
}
}
}
//输出结果:DCL懒汉式单例 Thread-0
使用反射破解DCL懒汉式单例的唯一性
public static void main(String[] args) throws Exception {
//创建对象1和2,通过DCLLazyMan.getInstance()方法
DCLLazyMan instance1 = DCLLazyMan.getInstance();
DCLLazyMan instance2 = DCLLazyMan.getInstance();
//创建对象3,通过反射获得构造方法的newInstance()方法
Constructor constructor = DCLLazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
DCLLazyMan instance3 = (DCLLazyMan)constructor.newInstance();
//输出结果:instance1和instance2相等吗? true
System.out.println("instance1和instance2相等吗? " + (instance1 == instance2));
//输出结果:instance2和instance3相等吗? false
System.out.println("instance2和instance3相等吗? " + (instance2 == instance3));
}
/*
结果分析:
对象1和2,都是通过正常方式创建的,所以指向的都是同一个对象
对象2和3,一个通过正常方式创建,一个通过反射方式创建,已经不是同一个对象了
说明反射可以破坏DCL懒汉式单例的唯一性
*/ 4、解决DCL懒汉式被反射破坏唯一性的问题 4.1、第一种方法:再加一重检查 //修改TCLLazyMan的构造方法为:
private TCLLazyMan(){
//第三重加锁判断
synchronized (TCLLazyMan.class){
if (lazyMan != null){
throw new RuntimeException("不要使用反射破坏类的唯一性");
}
}
}
//测试
public static void main(String[] args) throws Exception {
//创建对象1,通过DCLLazyMan.getInstance()方法
TCLLazyMan instance1 = TCLLazyMan.getInstance();
//创建对象2,通过反射获得构造方法的newInstance()方法
Constructor constructor = TCLLazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
TCLLazyMan instance2 = (TCLLazyMan)constructor.newInstance();
}
/*
结果分析:
Exception in thread "main" java.lang.reflect.InvocationTargetException
Caused by: java.lang.RuntimeException: 不要使用反射破坏类的唯一性
说明三重检测确实可以防止在对象已经被实例化后,再通过反射来创建对象的实例化
*/