【悲观锁】Synchronized和ReentrantLock的区别
【线程方法】多线程下 wait、notify、park、unpark 和 await、signal 的区别
【同步工具】CyclicBarrier和CountDownLatch的区别
双重校验锁(DoubleCheck)是一种在多线程环境下延迟加载并保证单例对象的创建的方法(懒汉模式)。
public class Singleton {
private static volatile Singleton instance;
// 私有构造方法,防止外部实例化
private Singleton() {
// 可以包含初始化操作
}
// 获取单例实例的方法
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 创建单例对象
}
}
}
return instance;
}
}
这里使用了 volatile
修饰 instance
变量,以确保多线程环境下对 instance
的读写操作是原子的,并且防止指令重排序。
1、第一次检查(非空判断): 在进入同步块之前,先检查 instance
是否为 null
。如果 instance
已经被初始化,就直接返回当前的实例,避免进入同步块,提高性能。若将同步锁直接加到方法上,则每次调用该方法都会争抢锁,并且进入和退出同步块都需要发生上下文切换的开销。
2、第二次检查(非空判断): 在同步块内,再次检查 instance
是否为 null
。避免初次创建时多个线程都通过了第一次检查,再依次进入同步块时,可能会重复创建实例。这是为了确保其他线程在当前线程获取锁之前已经创建了实例,避免重复创建实例。
3、volatile则是防止
防止指令重排序
假设线程A首先执行 getInstance()
方法,然后执行了双重检查中的第一次检查,判断 instance
为 null
,进入同步块,但在同步块内的第二次检查之前,线程B也执行了 getInstance()
方法,由于线程A还未完成实例化,线程B也会通过第一次检查,然后等待线程A执行完同步块。
此时,如果没有使用 volatile
关键字修饰 instance
,那么在线程A执行 instance = new Singleton();
这一步时,可能会发生指令重排序,导致实例在内存中的分配和初始化的顺序被颠倒。
- 分配内存空间给
instance
- 在内存空间中创建
Singleton
对象 - 将
instance
指向分配的内存空间
在多线程环境下,线程B可能在线程A执行完第一步和第二步之后,在同步块内的第二次检查时,发现 instance
不为 null
,于是直接返回了一个未完全初始化的实例,导致错误。