单例模式的五种创建方式

第一种,饿汉模式

总的来说,单例模式是将类的构造方法私有化,同时保留一个 static 的唯一实例,用于返回。 饿汉模式,在类加载的时候就创建实例,也即定义 static 的 instance 是就调用私有构造器创建实例, 通过 getInstance 方法会返回唯一的实例。这种方式能够保证线程安全,但是不能躲开反射机制的攻击。 事实上前四种方式都不能躲开反射机制的攻击,反射机制,可以通过设置 constructor.setAccessible(true); 强制调用类的私有域和私有方法,也就是说私有的构造器失去作用了。为了应对这种攻击, 可以在构造其中判断单例是否已经构造,如果是,则抛出异常。

第二种,懒汉模式

懒汉模式并不在类加载时创建实例,而在 getInstance 被调用时进行单例创建,这样做的好处是延迟加载。 但是线程不安全,在多线程下不能正常工作,为了保证线程安全,需要给 getInstance 方法增加同步锁, 当然这无疑会降低效率。另外,这种方式也需要考虑java反射攻击。

第三种,使用静态内部类实现

这种方式在单例中增加一个内部类 builder,内部类 builder 中保存一个静态不可变的单例对象, 改单例对象 instance 在内部类构建的时候调用私有单例构造器实例化,类似于饿汉模式。 单例中的 getInstance 方法返回 builder 中的 instance,也即只有到 getInstance 被调用时 单例对象随着 builder 而实例化。这样既能保证延迟加载,又能保证线程同步。

第四种,使用双重校验锁实现

这是对懒汉模式的改进(不过该改进似乎更糟糕),为了保证懒汉模式中的线程同步, 双重校验锁模式不为 getInstance 方法加锁,而是在该方法内部,在 instance 创建时给单例类加锁。 一般不推荐使用该种方式。

第五种,使用枚举类型实现

用一个包含单个元素的枚举类型实现单例,由于枚举元素就是一个静态的不可变的该类的实例, 并且枚举类型构造方法私有,可以任意添加各种方法。这种实现单例的方法较好的避免了反序列化攻击, 同时线程安全,不过没有延迟加载。是一种较好的方式,可以在新的项目中尝试使用。

enum EnumSingleton {
    INSTANCE;
    public void doSomeThing() {
    }
}