单例设计模式
单例模式(Singleton Pattern)涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
饿汉式
饿汉式就是在类加载的时候就进行初始化,基于classloader机制避免了多线程的同步问题。
这种方式比较常用,但容易产生垃圾对象,浪费内存。
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1 (){}
public static Singleton1 getInstance() {
return instance;
}
}
但是,该单例代码能受到反射的破坏;如果该类实现了Serializable接口,还可以通过反序列化重写readResolve()方法来破环单例。
public class Singleton1 implements Serializable {
private Singleton1() {
if (INSTANCE != null) {
throw new RuntimeException("单例对象不能重复创建");
}
System.out.println("private Singleton1()");
}
private static final Singleton1 INSTANCE = new Singleton1();
public static Singleton1 getInstance() {
return INSTANCE;
}
public Object readResolve() {
return INSTANCE;
}
}
- 构造方法抛出异常是防止反射破坏单例
readResolve()
是防止反序列化破坏单例
但是无法防止 unsafe 破环单例
枚举饿汉式
这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton2 {
INSTANCE;
public static Singleton2 getInstance() {
return INSTANCE;
}
}
枚举饿汉式能天然防止反射、反序列化破坏单例
懒汉式
懒汉式单例的主要特点就是被外部类调用的时候内才会创建实例。
1、不加锁(非线程安全)
public class Singleton3 {
private Singleton3() {}
private static Singleton3 INSTANCE = null;
public static Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
}
2、方法加锁(线程安全)
public class Singleton3 {
private Singleton3() {}
private static Singleton3 INSTANCE = null;
public static synchronized Singleton3 getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton3();
}
return INSTANCE;
}
}
实际上,只有第一次创建单例对象的时候需要加同步锁,后续掉用反而会因为同步锁而阻塞,导致程序性能下降。
3、双重校验锁(DCL)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton4 {
private Singleton4() {}
private static volatile Singleton4 INSTANCE = null; // 可见性,有序性
public static Singleton4 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton4.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton4();
}
}
}
return INSTANCE;
}
}
1、为何必须加 volatile:
INSTANCE = new Singleton4()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造- 如果线程1 先执行了赋值,线程2 执行到第一个
INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
4、静态内部类
这种方式能达到双检锁方式一样的功效,但实现更简单。
对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。
这种方式只适用于 静态域 的情况,双检锁方式可在 实例域需要延迟初始化时 使用。
public class Singleton5 {
private Singleton5() {}
private static class Holder {
static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return Holder.INSTANCE;
}
}
内部类是在方法调用之前初始化的,巧妙的避免了线程安全问题。
静态内部类的写法既没有饿汉式的内存浪费问题,也没有加锁带来的性能开销,避免了双检锁的缺点。
JDK 中单例的体现
- Runtime 体现了饿汉式单例模式
- System中的Console 体现了双检锁懒汉式单例
- Collections 中的 EmptyNavigableSet 内部类懒汉式单例
- ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
- Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例