定义

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

场景

重量级对象,不需要多个实例:例如线程池,数据库连接池。

懒汉模式

延迟加载,只有在真正使用的时候才开始实例化。

  1. class LazySingleTon {
  2. private volatile static LazySingleTon instance;
  3. private LazySingleTon(){
  4. }
  5. public static LazySingleTon getInstance() {
  6. if (instance == null) {
  7. synchronized (LazySingleTon.class) {
  8. if (instance == null) {
  9. instance = new LazySingleTon();
  10. }
  11. }
  12. }
  13. return instance;
  14. }
  15. }

关键点:

  1. 既然只能有一个实例,那么就不允许外部new这个对象,因此将构造函数设置为private;
  2. 既然没有外部对象,那么就需要将getInstance设置为静态方法,静态方法内部只能访问静态变量和静态方法,因此instance也必须是静态的,再加上没有不给外部访问,因此是private;
  3. 要解决多线程问题,就要用到sychronized,之所以有两个instance == null的判断是因为
  • 第一个判断是为了提升性能,当有instance的时候直接返回即可,不用涉及到锁的争夺;
  • 第二个判断是为了安全,如果不加的话,
    • 第一个线程进来,new对象,释放锁,停住;
    • 第二个线程进来,new对象,释放锁;
    • 导致new了两个对象,不符合单例。
  1. 此外,需要加上volatile来防止指令重排;

总结:

  1. 懒汉模式比较麻烦;
  2. 需要考虑线程安全问题;
  3. 需要double check 该 instance是否存在,容易忘记;
  4. 需要加volatile防止指令重排;

饿汉模式

不管用不用,我先将实例new出来;

class HungrySingleton {
    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return instance;
    }
}

关键点:

  1. 和懒汉一致的点:
  • 构造函数是private;
  • getInstance是static;
  • instance是private static;
  1. 在类初始化的时候直接将instance给new出来;
  2. 因为类只会被加载一次,静态变量的赋值是在初始化过程中完成的,通过该方式保证了线程安全的问题。

静态内部类

有点像懒汉+恶汉。

class InnerClassSingleton {

    private static class InnerClassHolder {
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    private InnerClassSingleton() throws Exception {
        if (InnerClassHolder.instance != null) {
            throw new Exception("不允许多个实例");
        }
    }

    public static InnerClassSingleton getInstance() {
        return InnerClassHolder.instance;
    }
}

关键点:

  1. 该类加载的时候不会去初始化示例;
  2. 但是在调用getInstance方法时,因为要访问InnerClassHolder的instance属性,因此会去进行内部类的加载和初始化,这一步由JVM来保证线程安全。

注意,在构造函数中加判断是为了防止反射攻击,导致仍然可以new出多个实例。

枚举

枚举的优点就是能抵抗反射攻击。