单例模式
定义
单例模式用来保证一个类在运行其间只有一个实例会被创建,另外单例模式还提供全局唯一的访问实例的接口
实现单例模式的方法
懒加载(线程不安全)
1 | public class Singleton { |
懒加载模式,当有多个线程并行调用getInstance()方法的时候,会有多个实例被创建,因此它是线程不安全的,也就是说在多线程下不能正常工作。
懒加载(线程安全)
该模式在上面的方法上加一个同步锁(synchronized)即可:
1 | public static synchronized Singleton getInstance() { |
虽然做到了线程安全,但是效率也受到了影响。因为只有在任何时候只能有一个线程调用getInstance()方法,同步操作有在第一次需要创建实例的时候才需要。这就引出了双重校验锁。
双重校验加锁(线程安全)
双重检验加锁模式(double checked locking pattern)是一种使用同步块加锁的方法,因为会有两次检查 uniqueInstance == null, 一次实在同步块外,一次是在同步块内。
1 | public static Singleton getSingleton() { |
为什么要两次检查 uniqueInstance == null
因为可能会有两个线程同时进入同步块外的if,如果不进行二次校验的话有可能会生成多个实例
但是这个依然存在问题,主要是uniqueInstance = new Singleton();不是一个原子性操作,这句话在JVM中大概做了以下三件事:
- 给uniqueInstance分配内存
- 调用Singleton的构造函数来初始化成员变量
- 将uniqueInstance对象指向分配的内存空间(uniqueInstance != null)
但是JVM的即时编译器存在指令重排优化,也就是说第2、3步的执行顺序不能保证,有可能2先执行,也可能3先执行,如果有两个线程同时执行2和3,然后就理所当然的报错。
只需要将uniqueInstance变量声明成volatile就可以了
1 | public class Singleton { |
使用volatile的一个重要原因是利用volatile禁止指令重排序优化的特性,从【先行发生原则】的角度理解的话,就是对于volatile变量的写操作都发生与后面对这个变量的读操作之前。
特别注意的是java5以前的版本即使使用了volatile的双重校验锁还是有问题的,原因在于java 5以前的JMM(Java Memory Model)存在缺陷,即使将变量声明成volatile也不能完全避免重排序。
急加载static final field(线程安全)
这个方法很简单,因为单例的实例都被声明成static和final了,在第一次加载类到内存时就会被初始化,所以本身就是线程安全的。
1 | public class Singleton{ |
该方法的缺点就在于它不是一种懒加载模式,即使客户端没有调用getInstance()方法也会创建实例。
静态内部类static netsted class (线程安全)
1 | public class Singleton { |
这种写法使用JVM本身机制保证了线程安全问题。由于SingletonHolder是私有的,除了getInstance()之外没有办法访问它,因此它是懒加载。同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖jdk版本。
枚举Enum(线程安全)
非常简单
1 | public enum EasySingleton{ |
通过EasySingleton.INSTANCE来访问实例,创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。而且这比调用getInstance()方法简单多了。




