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();
这段代码其实是分为三步执行:
- 为 instance 分配内存空间
- 初始化 instance
- 将 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;}}