本文主要是介绍巨详细分析单例模式!禁止套娃?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
单例模式
- 饿汉模式-多线程安全
- 懒汉模式-多线程不安全
- 懒汉模式-多线程安全
- 懒汉模式-双重校验锁(DCL,即 double-checked locking)-多线程安全
- 静态内部类-多线程安全
- 枚举-多线程安全且禁止反射
- 强行使用反射破环枚举单例
- 反射在双重校验锁创建下多个对象
- 双重校验锁禁止反射
- 双重校验锁不实例化对象直接反射
- 使用标志防止双重校验锁下多次反射
- 套娃永无止境
Java最简单的设计模式之一?
属于创建型模式,指一个类负责创建自己唯一的一个对象,对外提供一个的可以直接访问该对象的方式,而不需要实例化该类对象。
1.只能有一个实例
2.自己创建自己的唯一实例
3.必须对外提供该实例
安全是指普通情况下多线程安全,但除枚举方式外,均可通过反射获取多个对象
饿汉模式-多线程安全
public class HungryMan {private static HungryMan hungryMan=new HungryMan();private HungryMan(){}public static HungryMan getInstance(){return hungryMan;}
}
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
懒汉模式-多线程不安全
public class LazyMan {private static LazyMan lazyMan;private LazyMan(){}public static LazyMan getInstance(){if (lazyMan==null) {lazyMan = new LazyMan();}return lazyMan;}
}
public static void main(String[] args) {System.out.println(LazyMan.getInstance());for (int i = 0; i < 10; i++) {new Thread(()->{System.out.println(LazyMan.getInstance());}).start();}}
运行如下,众所周知,当多个线程同时在lazyMan对象即将赋值时失去CPU调度权,之后继续运行,就会有多个不同的对象产生
懒汉模式-多线程安全
public class LazyMan {private static LazyMan lazyMan;private LazyMan(){}//加锁public static synchronized LazyMan getInstance(){if (lazyMan==null) {lazyMan = new LazyMan();}return lazyMan;}
}
优点:这种实现就是在方法上加了锁,保证了多线程安全,
缺点:频繁获取该单例对象时效率有所下降。
懒汉模式-双重校验锁(DCL,即 double-checked locking)-多线程安全
public class LazyMan {private static LazyMan lazyMan;private LazyMan() {}public static LazyMan getInstance() {//第一个if提高效率,多个线程只有在lazyMan为null时才会等待锁,如果已经创建过单例对象则直接返回if (lazyMan == null) {synchronized (LazyMan.class) {//确保单例安全,lazyMan为null时,只有第一个获得锁的线程才会创建该单例对象,其余线程会直接返回if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}
}
JDK1.5
是不是看起来没毛病?
然鹅, lazyMan = new LazyMan() 的操作并不是原子性的,需要经过:
- 为这个对象分配内存
- 执行构造方法,初始化对象
- 将对象指向内存空间
其中,步骤2和3可能会出现指令重排,
也就是执行了步骤1后,接着执行了步骤3,
假如其他线程此时到了第一个if (lazyMan == null)判断,会发现该对象的引用不为null,于是返回了该单例对象
但此时对象尚未完成初始化操作,
那么就拿到了一个不安全的单例对象
所以需要禁止指令重排,使用volatile修饰该对象即可保证线程安全
public class LazyMan {private volatile static LazyMan lazyMan;private LazyMan() {}public synchronized static LazyMan getInstance() {if (lazyMan == null) {synchronized (LazyMan.class) {if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}
}
静态内部类-多线程安全
public class Singleton {private Singleton(){}public static Singleton getInstance(){return InnerClass.instance;}public static class InnerClass{private static final Singleton instance = new Singleton();}
}
只有通过显式调用 getInstance 方法时,才会实例化 instance,可以实现懒加载
枚举-多线程安全且禁止反射
枚举在编译时会继承枚举类,并声明为final,天生防止反射,JDK1.5
public enum Singleton1{SINGLETON;public static Singleton1 getSingleton(){return SINGLETON;}
}
public class Test {public static void main(String[] args) {Singleton1 singleton1 = Singleton1.SINGLETON;//Singleton1 singleton1 = Singleton1.getSingleton();//等价于上Singleton1 singleton2 = Singleton1.SINGLETON;//Singleton1 singleton2 = Singleton1.getSingleton();//等价于上System.out.println("singleton1 = " + singleton1);System.out.println("singleton2 = " + singleton2);System.out.println(singleton1 == singleton2);//true}
}
ctrl+鼠标左键
若使用枚举的构造实例化对象则抛出异常
强行使用反射破环枚举单例
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Singleton1 singleton1 = Singleton1.SINGLETON;Constructor<Singleton1> declaredConstructor = Singleton1.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);Singleton1 singleton2 = declaredConstructor.newInstance();System.out.println(singleton1==singleton2);}
}
结果会失败并抛出异常
反射在双重校验锁创建下多个对象
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyMan instance = LazyMan.getInstance();//创建单例对象Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//获取LazyMan的无参构造器declaredConstructor.setAccessible(true);//设置构造器可操作LazyMan LazyMan1 = declaredConstructor.newInstance();//通过私有构造器实例化对象LazyMan LazyMan2 = declaredConstructor.newInstance();System.out.println("单例instance = " + instance);System.out.println("反射LazyMan1 = " + LazyMan1);System.out.println("反射LazyMan2 = " + LazyMan2);}
可以看到,三个对象完全不同,因为反射是通过操作私有构造器的方式创建出来的
双重校验锁禁止反射
public class LazyManBan {private volatile static LazyManBan lazyMan;private LazyManBan() {if (lazyMan != null) {throw new RuntimeException("请勿使用反射!");}}public static synchronized LazyManBan getInstance() {if (lazyMan == null) {synchronized (LazyManBan.class) {if (lazyMan == null) {lazyMan = new LazyManBan();}}}return lazyMan;}
}
public class Test {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyManBan instance = LazyManBan.getInstance();//创建单例对象Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyManBan LazyManBan1 = declaredConstructor.newInstance();LazyManBan LazyManBan2 = declaredConstructor.newInstance();System.out.println("单例instance = " + instance);System.out.println("LazyManBan1 = " + LazyManBan1);System.out.println("LazyManBan2 = " + LazyManBan2);}
}
反射无法使用,你以为结束了吗?
反射无法使用是因为使用了getInstance()方法将单例对象lazyMan实例化
假如不赋值呢?也就是直接使用反射获取对象
双重校验锁不实例化对象直接反射
public class Test {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyManBan LazyManBan1 = declaredConstructor.newInstance();LazyManBan LazyManBan2 = declaredConstructor.newInstance();LazyManBan LazyManBan3 = declaredConstructor.newInstance();System.out.println("LazyManBan1 = " + LazyManBan1);System.out.println("LazyManBan2 = " + LazyManBan2);System.out.println("LazyManBan3 = " + LazyManBan3);}
}
对象还是被创建出来了
原因就是因为防止反射判断的是静态的lazyMan
是一个已经定义好了的对象
然而反射每次都是通过构造函数创建的对象,并没有将静态的lazyMan实例化,也就是两者没什么关系
使用标志防止双重校验锁下多次反射
public class LazyManBanFlag {private static boolean flag = false;private volatile static LazyManBanFlag lazyMan;private LazyManBanFlag() {if (!flag) {flag=true;}else {throw new RuntimeException("请勿使用反射!");}}public static synchronized LazyManBanFlag getInstance() {if (lazyMan == null) {synchronized (LazyManBanFlag.class) {if (lazyMan == null) {lazyMan = new LazyManBanFlag();}}}return lazyMan;}
}
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyManBan instance = LazyManBan.getInstance();//创建单例对象Constructor<LazyManBanFlag> declaredConstructor = LazyManBanFlag.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyManBanFlag LazyManBanFlag1 = declaredConstructor.newInstance();LazyManBanFlag LazyManBanFlag2 = declaredConstructor.newInstance();System.out.println("单例instance = " + instance);System.out.println("LazyManBanFlag1 = " + LazyManBanFlag1);System.out.println("LazyManBanFlag2 = " + LazyManBanFlag2);}
}
此时无论是正常获取对象+反射
还是直接多次反射
都会抛出异常
套娃永无止境
你以为这就安全了吗?
反射也可以直接操作变量,private?
每次反射创建一个对象,设置标志为 false
…
这篇关于巨详细分析单例模式!禁止套娃?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!