线程安全
约 1257 字大约 4 分钟
2026-02-26
- 作用域:字段声明
- 作用:用于修饰变量,被修饰的变量被读取时将不会从线程缓存中读取,而是强制从主存中读取,保证了该变量的可见性
class Record {
public static volatile int count;
}在以上的示例中,使用volatile修饰变量count,能够保证任意线程都能读取到变量的最新值,而不是只读取自身的缓存却对其他线程的更新浑然不知
volatile 作用
计算机为了最大化利用 CPU 的运算单元,编译器与 CPU 硬件将在单线程数据依赖不受破坏的前提下对指令进行重排 这一优化在多线程场景下可能会导致致命的逻辑错误,例如经典的单例实现:
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关键字,线程在访问单例方法时,会先检查对象是否为空,若为空则使用悲观锁synchronized避免竞争,二次确认对象是否为空,避免等待锁时其他线程已经完成初始化后重复初始化 对象的创建底层需要以下指令:
new分配内存:在堆内存中分配一块空白的内存区域,并且将指向该内存的内存地址压入操作栈invokespecial调用对象<init>方法(构造函数)初始化对象- 将内存地址赋值给
instance变量 这三个指令中,2 与 3 都依赖于 1,因此不能对 1 进行指令重排,但 2 和 3 可以,在单线程下 2 与 3 的执行更改并不影响结果。但若是多线程下,A 进入synchronized块创建对象,若先执行了 3 而在对象初始化前赋值地址给了变量,B 此时访问该方法,instance == null将会返回 false,直接获取了这个没有完成初始化的对象 综上,需要使用volatile禁止该变量的指令重排
synchronized
- 作用域:方法或包装代码块上
- 作用:在同一时间内,只允许一个线程进入被修饰的代码块,实现互斥
class Record {
private static volatile int count;
private final Object lock = new Object();
public int getIncr() {
synchronized (lock) {
count++;
return count;
}
}
}利用对象中Monitor(由 JVM 放在对象头中的一个与对象关联的同步控制结构),通过获取它才能获得代码块的进入权(可以看作锁)。
synchronized (lock) {}:获取 lock 对象public synchronized void increment() {}:获取this,也就是当前对象,这意味着该对象所有synchronized修饰的方法同时互斥public static synchronized void increment() {}:获取Record.class对象(类对象),同一个类中所有被synchronized同时互斥
Atomic
对一操作a++,涉及到三个操作
int tmp = a; // 读取
int tmp = a + 1; // 修改
a = temp; // 写入*代码不严谨,但大致如此
因此a++并不是一个院子操作,在上述的操作间的间隔中始终存在其他线程插入的可能 Atomic 类使用[[悲观锁&乐观锁#CAS(Compare And Swap)|CAS]]的机制实现乐观锁,能够保证了数值操作的并发安全
线程的协调与同步
wait()、notify()、notifyAll()是Object类中定义的方法,这三个方法必须在synchronized方法或方法块中调用,且只能调用当前拥有的锁对象,否则会抛出IllegalMonitorStateException异常
wait()
使线程自愿释放当前持有对象的锁,并进入等待状态,直到其他线程调用该对象的nitify()或notifyAll()方法通知其恢复执行 从runnable状态更新为waiting状态
notify()
唤醒当前持有对象上等待的线程,若有多个线程等待,则由 JVM 决定唤醒 调用notify()后并不会马上将锁释放,而是等到当前代码块执行完成后才去通知 将某一个处于waiting的线程更新为runnable状态
notifyAll()
唤醒当前持有对象上所有的等待线程,使这些线程从waiting状态更新为runnable状态
使用场景
生产者-消费者模型
设置一个锁对象和一个消息队列 当消费者完成消费,即消息队列中为空时,释放lock对象,进入等待状态 生产者向消息队列中放置消息,完成放置后通知该锁对象上的等待线程,也就是消费者,通知其消费
private int data;
private boolean available = false;
public synchronized int get() {
while (!available) {
try {
wait(); // 没数据,消费者等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
available = false;
notifyAll(); // 通知生产者可以生产了
return data;
}
public synchronized void put(int value) {
while (available) {
try {
wait(); // 缓冲区有数据,生产者等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
data = value;
available = true;
notifyAll(); // 通知消费者可以消费了
}#review
