Java并发-关键字
大约 3 分钟
Java并发-关键字
核心理论
1.1 并发关键字概述
Java提供了多个用于并发编程的关键字,这些关键字是实现线程安全的基础。核心并发关键字包括:synchronized、volatile、final、static、wait、notify、notifyAll、Thread.sleep()、Thread.yield()等。
1.2 关键字的内存语义
- synchronized:保证原子性、可见性和有序性
- volatile:保证可见性和有序性,不保证原子性
- final:保证对象初始化的安全性,被final修饰的字段不可变
1.3 Happens-Before规则
Happens-Before规则定义了操作之间的可见性关系,是理解Java内存模型的基础:
- 程序顺序规则:线程中的每个操作Happens-Before于该线程中后续的操作
- 监视器锁规则:解锁操作Happens-Before于后续对同一锁的加锁操作
- volatile变量规则:对volatile字段的写操作Happens-Before于后续对同一字段的读操作
- 线程启动规则:Thread.start()操作Happens-Before于线程中的任何操作
- 线程终止规则:线程中的所有操作Happens-Before于线程的终止检测
代码实践
2.1 volatile关键字的正确使用
public class VolatileExample {
private volatile boolean flag = false;
private int count = 0;
public void writer() {
flag = true; // volatile写
count = 1; // 普通写
}
public void reader() {
if (flag) { // volatile读
System.out.println(count); // 可能输出0,因为普通写不具有volatile的内存语义
}
}
}
2.2 synchronized与volatile的组合使用
public class SyncVolatileExample {
private volatile int state = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
state++;
}
}
public int getState() {
return state; // volatile保证可见性
}
}
2.3 wait/notify实现线程协作
public class WaitNotifyExample {
private final Object lock = new Object();
private boolean condition = false;
public void waitForCondition() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait(); // 必须在循环中检查条件
}
// 处理业务逻辑
}
}
public void setCondition() {
synchronized (lock) {
condition = true;
lock.notifyAll(); // 唤醒所有等待线程
}
}
}
设计思想
3.1 关键字的底层实现
- synchronized:JDK 6前基于对象监视器(monitor)实现,JDK 6后引入偏向锁、轻量级锁和重量级锁
- volatile:通过内存屏障(Memory Barrier)实现,禁止指令重排序
- final:通过编译器和处理器确保字段初始化完成后才能被访问
3.2 无锁编程与CAS
Java中的原子类(如AtomicInteger)使用CAS(Compare-And-Swap)操作实现无锁并发控制,CAS基于硬件指令实现,比锁机制更轻量。
public class AtomicIntegerExample {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // CAS操作
}
public int getCount() {
return count.get();
}
}
避坑指南
4.1 volatile的误用
- 不要将volatile用于复合操作(如i++),无法保证原子性
- 不要依赖volatile解决线程安全问题,它仅保证可见性和有序性
- 避免过度使用volatile,可能影响性能
4.2 synchronized的使用陷阱
- 避免同步String常量或基本类型包装类对象
- 不要在构造方法中使用synchronized
- 避免同步空对象或null
4.3 wait/notify的正确姿势
- wait()必须在同步块中调用
- 始终在循环中检查等待条件
- 优先使用notifyAll()而非notify(),避免线程饥饿
深度思考题
- volatile关键字如何保证可见性和有序性?底层实现原理是什么?
- synchronized和ReentrantLock的底层实现有何异同?
- final关键字如何保证对象的初始化安全性?
思考题回答:
volatile通过内存屏障实现可见性和有序性。写操作后插入StoreStore屏障和StoreLoad屏障,读操作前插入LoadLoad屏障和LoadStore屏障,禁止指令重排序并强制刷新缓存。
相同点:都实现了可重入锁机制。不同点:synchronized是JVM内置锁,由JVM自动管理;ReentrantLock是API层面的锁,需要手动释放,支持中断、超时和公平锁。
final字段在构造方法中初始化完成后,会通过编译器确保其他线程只能看到初始化完成的值。JVM禁止将final字段的写操作重排序到构造方法之外,确保对象引用对其他线程可见时,其final字段已经初始化完成。