今天整理一下单例模式。
单例模式应该是大学课程中,最先接触的设计模式,主要目的是保证生产出来的是一个实例。需要注意三点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
第一种情况:饿汉式
/*
* 饿汉式在不管调没调用方法,都会在类加载的时候事先声明一个对象,是线程安全的,
* 因为每次在类加载的时候对其进行声明,但是会造成资源浪费。
*/
//饿汉式
public class SingleHungry {
private static SingleHungry singleHungry = new SingleHungry();
private SingleHungry() {
}
public static SingleHungry getInstance(){
return singleHungry;
}
}
第二种情况:懒汉式
/*
* 懒汉式顾名思义,只有用的时候会对其进行声明。但是他是线程不安全的。
* 不安全的情况看代码注释
*/
public class SingleLazy {
private static SingleLazy singleLazy;
private SingleLazy(){}
public static SingleLazy getInstance(){
if(singleLazy==null){
singleLazy = new SingleLazy();
}
return singleLazy;
}
}
//但是这种情况并不是线程安全的
第三种情况:DCL懒汉式,又叫双重检测锁机制
他并不是线程安全的,由于对对象进行实例化的时候,并不是一个原子性操作具体分析看代码:
public class SingleLazy {
private static SingleLazy singleLazy;
private SingleLazy(){
}
public static SingleLazy getInstance(){
//双重检测锁机制 DCL懒汉式
if(singleLazy==null){
synchronized (SingleLazy.class){
if(singleLazy==null){
singleLazy = new SingleLazy();//并不是一个原子性操作
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向空间
* 在底层经过指令重排可能会导致直接返回没有初始化完成的对象
* 必须加上volatile避免指令重排
* */
}
}
}
return singleLazy;
}
但是在加上volatile之后,避免强制重排之后,在反射机制下依旧不是一个安全的,仍旧会造成单例模式不安全的情况:
public class SingleLazy {
private volatile static SingleLazy singleLazy;
private SingleLazy(){
synchronized (SingleLazy.class){
if(singleLazy!= null){
throw new RuntimeException("不要试图反射破坏异常!");
}
}
System.out.println(Thread.currentThread().getName()+"ok");
}
public static SingleLazy getInstance(){
//双重检测锁机制 DCL懒汉式
if(singleLazy==null){
synchronized (SingleLazy.class){
if(singleLazy==null){
singleLazy = new SingleLazy();//并不是一个原子性操作
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向空间
* 在底层经过指令重排可能会导致多线程判断是没有初始化完成的对象
* 必须加上volatile避免指令重排
* */
}
}
}
return singleLazy;
}
//通过反射依旧能破坏单例模式
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
SingleLazy instance = SingleLazy.getInstance();
Constructor<SingleLazy> declaredConstructor = SingleLazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingleLazy instance1= declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
到这里为止,发现在反射能破坏单例模式,在借助反射进行破坏私有构造器的时候,Constructor.java中的newInstance()源码中描述:
第四种情况:通过枚举来实现单例
在.class文件中发现`
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
public enum EnumTest {
INSTANCE;
//此处是私有化的无参构造器,所以对其进行反射尝试获取他的私有化构造器实例化对象
private EnumTest() {
}
public EnumTest getInstance() {
return INSTANCE;
}
}
测试代码:
public enum EnumTest {
INSTANCE;
public EnumTest getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumTest> declaredConstructor = EnumTest.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumTest enumTest = declaredConstructor.newInstance();
System.out.println(enumTest);
}
}
但是在运行过后会发现出现
Exception in thread "main" java.lang.NoSuchMethodException: com.single.EnumTest.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.single.Test.main(EnumTest.java:15)
并没有出现预期的抛出不合法的参数异常,而是出现了一个NoSuchMethodException
所以对其字节码文件进行反编译后:
经过上述反编译过后,可以看出依旧是无参构造器,但是在枚举下其实是有参数的构造器
public enum EnumTest {
INSTANCE;
public EnumTest getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumTest> declaredConstructor = EnumTest.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumTest enumTest = declaredConstructor.newInstance();
System.out.println(enumTest);
}
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.single.Test.main(EnumTest.java:17)