Hungry Han Singleton : Initialize directly when the class is loaded
Advantages: simple, safe
Disadvantages: Sometimes the class is initialized when it is not required to load, hoping to delay loading.
code |
public class Singleton{ // Constructor private private Singleton(){};
// static members private static final Singleton singleton = new Singleton(); public static Singleton getInstance(){ return singleton; } } |
Lazy Singleton : Only create singletons when they are actually used
1. Single-threaded simple singleton
code |
public class Singleton{ // Constructor private private Singleton(){};
// static members private static Singleton singleton; public static Singleton getInstance(){ // Created when used for the first time if(singleton == null){ singleton = new Singleton(); } // Then return directly return singleton; } } |
Problems with multi-threading: multiple threads enter the if condition at the same time and create multiple instances, which violates the original intention of the singleton.
2. Use built-in lock protection: Use synchronized built-in locks to ensure that only one thread can enter the critical section
code |
public class Singleton{ // Constructor private private Singleton(){};
// static members private static Singleton singleton; // Use synchronized to lock the critical section public static synchronized Singleton getInstance(){ // Created when used for the first time if(singleton == null){ singleton = new Singleton(); } // Then return directly return singleton; } } |
The disadvantage is that every time a singleton is obtained, a lock must be obtained. Under high concurrency, the built-in lock will be upgraded to a heavyweight lock, which is expensive and has poor performance.
3. 双重检查锁:只在第一次创建的时候进行加锁,所以,先判断单例是否已被初始化,如果没有,加锁后再初始化。
代码 |
public class Singleton{ //构造器私有 private Singleton(){};
//静态成员 private static Singleton singleton; public static Singleton getInstance(){ if(singleton == null){ //一重检查 synchronized(Singleton.class){ //加锁 if(singleton == null){ //二重检查 singleton = new Singleton(); } } } //随后直接返回 return singleton; } } |
在进行第一重检查的时候不需要加锁,多个线程可以同时进入,但是只有一个线程能获取到锁并创建单例,其余线程随后可能获取到锁,但是无法通过第二重检查,所以直接返回单例。相比锁住整个getInstance方法,锁的粒度更小了,性能更好。
4. 双重检查锁+volatile: 表面上看双重检查锁天衣无缝了,但是需要注意:初始化单例的语句:
singleton = new Singleton();
并不是一个原子操作,经过汇编之后大致会被分成三个步骤:
1)分配一块内存M.
2) 在内存M上初始化Singleton对象
3)把M的地址赋给singleton变量
编译器、CPU都可能会对没有内存屏障和数据依赖关系的操作进行重排序,上述三个指令可能被优化成1)3)2)。这就可能导致线程A先进入临界区并且分配了内存,把地址赋给了singleton变量,但是还没有初始化对象,这时候线程B调用getInstance方法,发现singleton != null,直接返回一个未初始化的对象。
解决方法是用volatile关键字禁止指令重排。
代码 |
public class Singleton{ //构造器私有 private Singleton(){};
//静态成员,使用volatile保持可见性,禁止指令重排 private static volatile Singleton singleton; public static Singleton getInstance(){ if(singleton == null){ //一重检查 synchronized(Singleton.class){ //加锁 if(singleton == null){ //二重检查 singleton = new Singleton(); } } } //随后直接返回 return singleton; } }
|
实际上,只有很低版本的 Java 才会有这个问题。我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。
静态内部类实现单例:虽然双重检查锁能实现高性能、线程安全的单例模式,但是写法繁琐,利用静态内部类可以实现简单且安全的单例。
代码 |
public class Singleton{ //构造器私有 private Singleton(){};
//静态内部类 private static class LazyHolder{ //通过final保障初始化的线程安全 private static final Singleton singleton = new Singleton(); }
public static final Singleton getInstance(){ return LazyHolder.singleton; } } |
在外部类被加载的时候并不会创建内部类的实例对象,只有getInstance()被调用的时候才会去加载内部类并且初始化单例。
但是静态内部类有一个致命的缺点:外部无法传入参数