饿汉单例:类加载的时候就直接初始化
优点:简单、安全
缺点:有时候不需要类加载的时候就初始化,希望延迟加载
代码 |
public class Singleton{ //构造器私有 private Singleton(){};
//静态成员 private static final Singleton singleton = new Singleton(); public static Singleton getInstance(){ return singleton; } } |
懒汉单例:真正使用的时候才创建单例
1. 单线程简单单例
代码 |
public class Singleton{ //构造器私有 private Singleton(){};
//静态成员 private static Singleton singleton; public static Singleton getInstance(){ //第一次使用的时候创建 if(singleton == null){ singleton = new Singleton(); } //随后直接返回 return singleton; } } |
多线程下存在的问题:多个线程同时进入if条件,创建多个实例,违背了单例初衷。
2. 使用内置锁保护:使用synchronized内置锁确保只有一个线程可以进入临界区
代码 |
public class Singleton{ //构造器私有 private Singleton(){};
//静态成员 private static Singleton singleton; //使用synchronized锁住临界区 public static synchronized Singleton getInstance(){ //第一次使用的时候创建 if(singleton == null){ singleton = new Singleton(); } //随后直接返回 return singleton; } } |
缺点在于每次获取单例都要获取锁,高并发下内置锁会升级成重量级锁,开销大、性能差
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()被调用的时候才会去加载内部类并且初始化单例。
但是静态内部类有一个致命的缺点:外部无法传入参数