单例模式(Singleton Pattern)保证一个类仅有一个实例,并提供一个访问它的全局访问点。
饿汉式
饿汉式基于 classloader 机制避免了多线程的同步问题,会在第一次调用时就直接初始化,线程安全,但是可能会浪费空间。
可以在使用的时候再创建对象,于是出现了懒汉式。
1 2 3 4 5 6 7 8 9 10 11
| public class SingletonGreed {
public SingletonGreed() { }
private final static SingletonGreed SINGLETON = new SingletonGreed();
public static SingletonGreed getInstance() { return SINGLETON; } }
|
懒汉式
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式,并发下会出现重复创建对象的问题,线程不安全。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class SingletonLazy {
public SingletonLazy() { }
private static SingletonLazy SINGLETON;
public static SingletonLazy getInstance() { if (SINGLETON == null) { SINGLETON = new SingletonLazy(); } return SINGLETON; }
|
双重检测锁
这里给懒汉式引入了双重检测锁(DCL, Double Checked Locking),做到线程安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class SingletonLazy {
public SingletonLazy() { }
private volatile static SingletonLazy SINGLETON;
public static SingletonLazy getInstance() { if (SINGLETON == null) { synchronized (SingletonLazy.class) { if (SINGLETON == null) { SINGLETON = new SingletonLazy(); } } } return SINGLETON; } }
|
其中由于 SINGLETON = new SingletonLazy();
不是原子性操作,分为以下三步:
- 分配内存空间
- 执行构造函数,初始化对象
- 把对象指向这个空间
所以我们需要加上 volatile
关键字,防止指令重排。
反射破坏单例
懒汉式还有个问题是会被反射破坏掉单例状态,如下演示:
1 2 3 4 5 6 7
| SingletonLazy instance1 = SingletonLazy.getInstance(); Constructor<SingletonLazy> declaredConstructor = SingletonLazy.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); SingletonLazy instance2 = declaredConstructor.newInstance();
System.out.println(instance1); System.out.println(instance2);
|
静态内部类
静态内部类可以实现和双重检测锁一样线程安全的效果,实现起来更简单。
和上面的一样,这个也是不安全的,会被反射破坏单例。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class SingletonHolder {
private SingletonHolder() { }
public static class InnerClass { private static final SingletonHolder HOLDER = new SingletonHolder(); }
public static SingletonHolder getInstance() { return InnerClass.HOLDER; } }
|
枚举
枚举可以很好的防止反射,同样也是线程安全的。
1 2 3 4 5 6 7 8 9
| public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() { return INSTANCE; }
}
|
反射测试
1 2 3 4 5 6 7
| SingletonEnum instance1 = SingletonEnum.INSTANCE; Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class); declaredConstructor.setAccessible(true); SingletonEnum instance2 = declaredConstructor.newInstance();
System.out.println(instance1); System.out.println(instance2);
|