> 文档中心 > 23种设计模式之单例模式

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);    }}

23种设计模式之单例模式
可以知道该种方法是通过反射,得到私有构造器,通过私有构造器来创建对象,所以我们可以通过对构造器加一些控制来避免这种方式的破坏

避免通过反射破坏单例
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);    }}

23种设计模式之单例模式
那如何避免这种情况?可以通过添加标志量来避免

避免通过反射破坏单例
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);    }}

23种设计模式之单例模式

可以发现,某前使用的这些方法,总会有另一种方法来破坏单例模式,那是否有一种操作,使其无法通过反射来构造对象,从根本上解决这个问题?
答案是有的,那就是通过枚举,在通过构造器的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());    }}

23种设计模式之单例模式


java-单例模式 推荐狂神说

中评网简体版