vlambda博客
学习文章列表

设计模式探秘之单例模式

单例模式估计大家都很熟悉了,我们使用的Spring框架中Bean的默认作用域就是单例。但是,你真的会写单例吗?单例模式有几种写法呢?你知道反射可以破坏单例模式吗?下面我们就来探秘单例模式。
定义

保证一个类只有一个实例,并提供一个访问它的全局访问点。

饿汉式

类加载就创建对象,结果显而易见,输出“s1等于s2”。

public class Hungry { private Hungry() {  } private static Hungry single = new Hungry(); public static Hungry newInstance() { return single; } public static void main(String[] args) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Hungry s1 = Hungry.newInstance(); Hungry s2 = Hungry.newInstance(); if (s1 == s2) { System.out.println("s1等于s2"); } else { System.out.println("s1不等于s2"); } }}

这里还要说明一下,“饿汉式”有一丢丢的小问题,如果定义很多下面这样的占用内存的字节数组,长时间没有用到newInstance方法会造成内存浪费。

public class Hungry { private byte[] data = new byte[1024]; ...}
懒汉式
用的时候才去创建对象,这种方式和上面的“懒汉式”都是线程安全的方式。你有没有发现“懒汉式”体现了缓存的思想呢?
public class Lazy { private Lazy() {  } private static Lazy single = null; public synchronized static Lazy newInstance() { if(single == null) { single = new Lazy(); } return single; }}
双重检测加锁
public class DoubleDetection { private DoubleDetection() {  } private static DoubleDetection single = null; public static DoubleDetection getSingleton() { if(single == null) { synchronized (DoubleDetection.class) { if(single == null) { single = new DoubleDetection(); } }    } return single; }}
private volatile static DoubleDetection single = null;
静态内部类

该方式是“饿汉式”的改进版,这种方式很巧妙的实现了懒加载。

public class LazyHolder { private static class Holder {      private static final LazyHolder INSTANCE = new LazyHolder();  }  private LazyHolder (){}  public static LazyHolder getInstance() {      return Holder.INSTANCE;  }}

反射破坏单例模式

main方法中执行如下代码,会发现输出的居然不是单例,会不会觉得反射很邪恶呢设计模式探秘之单例模式

//获得构造器Constructor<Lazy> con = Lazy.class.getDeclaredConstructor();//设置为可访问con.setAccessible(true);//构造两个不同的对象Lazy singleton1 = con.newInstance();Lazy singleton2 = con.newInstance();//验证是否是不同对象System.out.println(singleton1);System.out.println(singleton2);

设计模式探秘之单例模式

枚举
public enum Recommend { INSTANCE; public Recommend getInstance(){ return INSTANCE; } public String getName() { return name; } private String name;}
public class Demo {  public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //获得构造器    Constructor<Recommend> con2 = Recommend.class.getDeclaredConstructor(); //设置为可访问    con2.setAccessible(true); //构造两个不同的对象 Recommend r1 = (Recommend) con2.newInstance();    Recommend r2 = (Recommend) con2.newInstance(); //验证是否是不同对象 System.out.println(r1); System.out.println(r2);  }}

为什么枚举能够严格的保证线程安全呢?我们看一下源码,枚举会直接抛异常

设计模式探秘之单例模式

我们测试一下结果,居然是NoSuchMethodException异常,好奇怪设计模式探秘之单例模式设计模式探秘之单例模式

设计模式探秘之单例模式

设计模式探秘之单例模式

像下面这样改一下代码再次运行,终于出现了Cannot reflectively ...设计模式探秘之单例模式设计模式探秘之单例模式

Constructor<Recommend> con2 = Recommend.class.getDeclaredConstructor(String.class, int.class);

思考总结

实际的开发中,我们不会真的主动用反射破坏单例,所以绝大多数情况下文中所列单例模式都是线程安全的。从单例模式的定义可以得出结论,一个类只有一个实例而且只能从一个全局访问点访问的情况下可以选用单例模式。