
8.4 七种单例模式实现方式
单例模式的实现方式比较多,主要分为在实现上是否支持懒汉模式,是否支持在线程安全中运用各项技巧。当然,也有一些场景不需要考虑懒汉模式的情况,会直接使用static静态类或属性和方法的方式,供外部调用。
接下来通过不同方式的实现,讲解单例模式。
8.4.1 静态类使用


这种静态类方式在日常的业务开发中很常见,它可以在第一次运行时直接初始化Map类,同时也不需要直到延迟加载再使用。在不需要维持任何状态的情况下,仅仅用于全局访问,使用静态类方式更加方便。在需要被继承及维持一些特定状态的情况下,适合使用单例模式。
8.4.2 懒汉模式(线程不安全)

单例模式有一个特点是不允许外部直接创建,也就是 new Singleton_01(),因此这里在默认的构造函数上添加了私有属性private。虽然采用此种方式的单例满足了懒汉模式,但是如果有多个访问者同时获取对象实例,就会造成多个同样的实例并存,没有达到单例的要求。
8.4.3 懒汉模式(线程安全)

此种模式虽然是安全的,但由于把锁加到方法中后,所有的访问因为需要锁占用,导致资源浪费。除非在特殊情况下,否则不建议用此种方式实现单例模式。
8.4.4 饿汉模式(线程安全)

这种方式与开头的第一个实例化 Map 基本一致,在程序启动时直接运行加载,后续有外部需要使用时获取即可。这种方式并不是懒加载,也就是说无论程序中是否用到这样的类,都会在程序启动之初进行创建。这种方式造成的问题就像一款游戏软件,可能游戏地图还没有打开,但是程序已经将这些地图全部实例化。在手机上最明显的体验就是打开游戏提示内存满了,造成手机卡顿。
8.4.5 使用类的内部类(线程安全)

使用类的静态内部类实现的单例模式,既保证了线程安全,又保证了懒汉模式,同时不会因为加锁而降低性能。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确地加载。这也是推荐使用的一种单例模式。
8.4.6 双重锁校验(线程安全)


双重锁的方式是方法级锁的优化,减少了获取实例的耗时。同时,这种方式也满足了懒汉模式。
8.4.7 CAS“AtomicReference”(线程安全)

Java 并发库提供了很多原子类支持并发访问的数据安全性,如:AtomicInteger、AtomicBoolean、AtomicLong 和 AtomicReference。AtomicReference<V> 可以封装引用一个V实例,上面支持并发访问的单例模式就是利用了这种特性。使用CAS的好处是不需要使用传统的加锁方式,而是依赖CAS的忙等算法、底层硬件的实现保证线程安全。相对于其他锁的实现,没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发。当然,CAS也有一个缺点就是忙等,如果一直没有获取到,会陷于死循环。
8.4.8 Effective Java作者推荐的枚举单例(线程安全)

Joshua J.Bloch是美国著名的程序员,他为Java平台设计并实现了许多功能,曾担任Google公司的首席Java架构师,是Effective Java的作者。
Joshua J.Bloch推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。这种方式解决了最主要的线程安全、自由串行化和单一实例问题。调用方式如下:

这种写法虽然在功能上与共有域的方法接近,但是它更简洁。即使在面对复杂的串行化或反射攻击时,也无偿地提供了串行化机制,绝对防止对此实例化。虽然这种方式还没有被广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
同时,我们也要知道在存在继承的场景下,此种方式是不可用的。