23种设计模式之单例模式
饿汉式与懒汉式
饿汉式
- 不让用户去自主的创建实例对象,而是自己创建好实例对象,让用户通过设置好的方法去调用
package com.cx;/ * 饿汉式 */public class SingletonHung { //事先准备实例对象 private static SingletonHung singletonHung=new SingletonHung(); //构造器私有,用户不能直接通过构造器来创建对象了 private SingletonHung() { } //提供一个方法对外使用 public static SingletonHung getSingletonHung(){ return singletonHung; }}
懒汉式
- 使用的时候再去创建
package com.cx;/ * 懒汉式 */public class SingletonLazy { private static SingletonHung singletonHung=null; //构造器私有 private SingletonLazy() { } public static SingletonHung singletonHungInstance(){ if (singletonHung==null){ singletonHung=new SingletonHung(); } return singletonHung; }}
注意:上述在多线程中,容易出现线程安全问题
DCL懒汉式
DCL单例模式:Double Check Lock缩写,即双重检查锁定,是单例模式中线程安全的懒汉式单例模式
package com.cx;/ * 懒汉式 */public class SingletonLazy { private static SingletonLazy singletonLazy=null; //构造器私有 private SingletonLazy() { } //双重检测模式 public static SingletonLazy singletonLazyInstance(){ if (singletonLazy==null){ synchronized (SingletonLazy .class){ if (singletonLazy==null){ singletonLazy=new SingletonLazy(); } } } return singletonLazy; } }
此时是否就安全了?
极端情况下,此模式也不安全,原因如下:
singletonHung=new SingletonHung();
此步骤不是一个原子性操作,实际上是有三步操作:
1. 分配内存空间2. 执行构造方法,初始化对象3. 把该对象指向这个空间
那么可能就会出现指令重排,即,若线程A执行顺序为1-3-2,线程B执行顺序为1-2-3;那么当线程A执行到1-3时,此时还没有实际生成对象,但是线程B认为singletonHung!=null,那么就会直接走return,那么线程B的得到的是没有完成构造的对象。那么现在为了避免指令重排,就要使用volatile关键字避免指令重排
package com.cx;/ * 懒汉式 */public class SingletonLazy { //volatile 避免指令重排 private static volatile SingletonLazy singletonLazy=null; //构造器私有 private SingletonLazy() { } //双重检测模式 public static SingletonLazy singletonLazyInstance(){ if (singletonLazy==null){ synchronized (SingletonLazy.class){ if (singletonLazy==null){ singletonLazy=new SingletonLazy(); } } } return singletonLazy; } }
那么经过上述操作了之后,是不是就安全了?
显然也不是很安全,因为还有一种方法可以破坏此模式
反射破坏单例模式1
package com.cx;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class Test29 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { SingletonLazy singletonLazyInstance = SingletonLazy.getSingletonLazyInstance(); Constructor<SingletonLazy> declaredConstructor = SingletonLazy.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有 SingletonLazy singletonLazy = declaredConstructor.newInstance(); //得到的两个对象不一样 System.out.println(singletonLazyInstance); System.out.println(singletonLazy); }}
可以知道该种方法是通过反射,得到私有构造器,通过私有构造器来创建对象,所以我们可以通过对构造器加一些控制来避免这种方式的破坏
避免通过反射破坏单例
package com.cx;/ * 懒汉式 */public class SingletonLazy { //volatile 避免指令重排 private static volatile SingletonLazy singletonLazy=null; //构造器私有 private SingletonLazy() { if (singletonLazy!=null){ throw new RuntimeException("不要视图通过使用反射破坏异常!"); } } //双重检测模式 public static SingletonLazy getSingletonLazyInstance(){ if (singletonLazy==null){ synchronized (SingletonLazy.class){ if (singletonLazy==null){ singletonLazy=new SingletonLazy(); } } } return singletonLazy; }}
那么如果两个都使用反射来构造对象?
反射破坏单例模式2
package com.cx;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class Test29 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Constructor<SingletonLazy> declaredConstructor = SingletonLazy.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有 SingletonLazy singletonLazy = declaredConstructor.newInstance(); SingletonLazy singletonLazy2 = declaredConstructor.newInstance(); //得到的两个对象不一样 System.out.println(singletonLazy); System.out.println(singletonLazy2); }}
那如何避免这种情况?可以通过添加标志量来避免
避免通过反射破坏单例
package com.cx;/ * 懒汉式 */public class SingletonLazy { //标志位 private static boolean flag=false; //volatile 避免指令重排 private static volatile SingletonLazy singletonLazy=null; //构造器私有 private SingletonLazy() { if (flag==false){ flag=true; }else{ throw new RuntimeException("不要视图通过使用反射破坏异常!"); } if (singletonLazy!=null){ throw new RuntimeException("不要视图通过使用反射破坏异常!"); } } //双重检测模式 public static SingletonLazy getSingletonLazyInstance(){ if (singletonLazy==null){ synchronized (SingletonLazy.class){ if (singletonLazy==null){ singletonLazy=new SingletonLazy(); } } } return singletonLazy; }}
那么此做法是不是就不会再出错了?显然不是,我们还可以通过反射获取其私有属性,进行修改
反射破坏单例模式3
package com.cx;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;public class Test29 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Constructor<SingletonLazy> declaredConstructor = SingletonLazy.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true);//关掉权限检测,就可以使用私有 Field flag = SingletonLazy.class.getDeclaredField("flag"); flag.setAccessible(true); SingletonLazy singletonLazy = declaredConstructor.newInstance(); flag.set("flag",false);//又将其修改回来 SingletonLazy singletonLazy2 = declaredConstructor.newInstance(); //得到的两个对象不一样 System.out.println(singletonLazy); System.out.println(singletonLazy2); }}
…
可以发现,某前使用的这些方法,总会有另一种方法来破坏单例模式,那是否有一种操作,使其无法通过反射来构造对象,从根本上解决这个问题?
答案是有的,那就是通过枚举,在通过构造器的newInstance来创建对象中,明确的表示:Cannot reflectively create enum objects
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
枚举类实现单例模式
Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。
package com.cx;public enum SingletonEnum { INSTANCE; //volatile 避免指令重排 private volatile User user = null; //构造器私有 private SingletonEnum() { /* if (user == null) { synchronized (User.class) { if (user == null) { user = new User(); } } } */ user = new User(); } //提供单例 public User getUser(){ return user; }}
package com.cx;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class Test29 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { User user = SingletonEnum.INSTANCE.getUser(); User user2 = SingletonEnum.INSTANCE.getUser(); //一模一样,构造器只会执行一次 System.out.println(user.hashCode()); System.out.println(user2.hashCode()); System.out.println("------------------"); Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); //这一步就会报异常 Cannot reflectively create enum objects SingletonEnum singletonEnum = declaredConstructor.newInstance(); User user3 = singletonEnum.getUser(); System.out.println(user3.hashCode()); }}
java-单例模式 推荐狂神说