1. 概念

保证类只有一个实例,让类自身负责保存它的唯一实例,并且类提供一个访问该实例的方法。

2. 单线程下的单例模式

public class Singleton {private static Singleton instance;private Singleton(){} //private构造方法,其他类无法访问public static Singleton getInstance(){//获得类实例的唯一全局访问点if(instance==null){instance = new Singleton();}return instance;}}

注意构造函数用private修饰,外界无法创建此类实例

3. 多线程下的单例模式

3.1. 双重检验锁实现单例模式(懒汉式)

public class Singleton {private volatile static Singleton instance;private Singleton(){} //private构造方法,其他类无法访问public static Singleton getInstance(){//获得类实例的唯一全局访问点if(instance == null){synchronized (Singleton.class){if(instance == null){instance = new Singleton();}}}return instance;}}

细说单例模式中的双重检查锁 – Decouple

终于搞懂双重检验锁实现单例模式了

Java并发常见面试题总结(中)

  • volatile用于禁止指令重排

instance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 instance 分配内存空间
  2. 初始化 instance
  3. 将 instance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance()后发现instance 不为空,因此返回 instance,但此时 instance 还未被初始化。

  • 双重检验主要是为了防止每次其他线程想要访问单例都会被阻塞

如果代码设计成如下:

public class Singleton {private volatile static Singleton instance= null;private Singleton() {};public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}

这样的话,锁的粒度太大了,多个线程同时调用 getInstance() 方法时,除一个线程之外,剩下所有线程都会被阻塞。但是那些线程都只是想访问一下类的唯一实例,这样也被阻塞了,程序的效率大大降低。

为了让程序的效率不那么低,我们缩小同步代码块的范围:

public class Singleton {private volatile static Singleton instance= null;private Singleton() {};public static Singleton getInstance() {if(instance == null){synchronized (Singleton.class){instance = new Singleton();}}return instance;}}

那这样行不行呢?答案还是不行。

这是由于假设此刻 instance 为 null,如果A,B两个线程同时判断 instance == null 成立,那么两个线程都会进行锁资源的争夺,如果 A 获取到锁资源,则 B 进行阻塞,待 A 完成实例化操作释放掉锁资源后,B 被唤醒,B仍然可以进行实例化操作,创建新的对象,那么便违背了单例模式只有一个实例对象的原则。

因此,需要在同步代码块中再次添加条件判断才行,这才有了双重检验。

3.2. 饿汉式

饿汉式是指在类加载时就初始化唯一实例,并且将其设置为不可变。

上面的懒汉式是在需要该实例时,才会实例化。

public class Singleton {private static final Singleton instance= new Singleton();private Singleton() {};public static Singleton getInstance() {return instance;}}