b站上看了狂神的视频讲的单例模式。
现在主要提炼以下几点:
1、单例模式有饿汉式和懒汉式 饿汉式占用内存较大。
2、多线程下单例失效,需双重检测锁模式 即DCL懒汉式
3、DCL懒汉式可能存在指令重排问题,需要加上volatile 保证原子性
4、用反射可以破坏上述volatile的DCL懒汉式
5、针对上述问题可以添加第三重检测。
6、如果创建对象不走LazyMan.getInstance(),都走反射创建,那么就会绕过第三重检测,即第三重检测失效。
7、添加标志位即可解决上述问题。
8、如果标志位被修改即标志位失效则问题依旧存在。
9、使用枚举类,枚举类自带单例并且反射不能破坏枚举,写在源码里的。
下面是实验代码,我尽量写的清楚些,大家可以去看视频理解。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class LazyMan {
//针对第三次实验出现的问题 ,做出的优化,添加标志位
private static boolean key = false;
private LazyMan() {
//针对第三次实验出现的问题 ,做出的优化,添加标志位
if(key == false) {
key = true;
}else {
throw new RuntimeException("在用反射破坏单例模式!");
}
//针对第二次实验出现的问题,做出的优化 ,第三重检测。
// if(lazyMan!=null) {
// throw new RuntimeException("在用反射破坏单例模式!");
// }
//第一次实验添加
System.out.println(Thread.currentThread().getName()+"--ok!");
}
private volatile static LazyMan lazyMan;
//1、双重检测锁模式的懒汉式单例 DCL懒汉式 防止多线程下的并发的多个线程
public static LazyMan getInstance() {
if(lazyMan == null) {
synchronized(LazyMan.class){
if(lazyMan==null) {
lazyMan = new LazyMan();
/*
* new不是一个原子性操作,分为三步: 分配内存 、初始化对象、将对象指向内存空间
* 可能存在指令重排的问题,即三步混乱重排
* 需要加上volatile 保证原子性 避免指令重排
*/
}
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
//第二次实验:用反射破坏上述volatile的DCL懒汉式
// LazyMan instance1 = LazyMan.getInstance();
// Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得LazyMan的构造器,因为是无参构造,所以参数为null
// declaredConstructor.setAccessible(true);//权限为true,则无视了构造器中的private,然后就可以通过反射来创建对象
// LazyMan instance2 = declaredConstructor.newInstance();
// System.out.println(instance1);
// System.out.println(instance2);//按照单例来说 这两个对象应该是一样的,但是他们不是一样的。则得出 :反射可以破坏单例
//第三次实验:针对第三重优化,如果创建对象不走LazyMan.getInstance(),都走反射创建,那么就会绕过第三重优化,即第三重优化失效。
// LazyMan instance1 = declaredConstructor.newInstance();
// LazyMan instance2 = declaredConstructor.newInstance();
// System.out.println(instance1);
// System.out.println(instance2);
//第四次实验,针对标志位优化,我们获得标志位 修改标志位,来破坏,所以需要用枚举类来使用单例,枚举类自带单例并且反射不能破坏枚举,写在源码里的
Field key = LazyMan.class.getDeclaredField("key");
key.setAccessible(true);
Constructor<LazyMan> declaredConstructor1 = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor1.setAccessible(true);
LazyMan instance1 = declaredConstructor1.newInstance();
key.set(instance1, false);
LazyMan instance2 = declaredConstructor1.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
//第一次实验:多线程下的单例
// public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
// }
}