线程同步
2026/1/15大约 6 分钟
线程同步
什么是线程同步
线程同步是指多个线程在访问共享资源时,通过某种机制协调它们的执行顺序,确保数据的一致性和正确性。
核心概念:
- 临界区(Critical Section):访问共享资源的代码段
- 互斥(Mutual Exclusion):同一时刻只有一个线程能访问临界区
- 原子性(Atomicity):操作要么全部完成,要么全部不完成
- 可见性(Visibility):一个线程对共享变量的修改,其他线程能立即看到
- 有序性(Ordering):程序执行的顺序按照代码的先后顺序执行
为什么需要同步
多线程访问共享资源时,如果没有同步机制,会导致数据不一致问题。
public class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作:读取 -> 加1 -> 写入
}
public int getCount() {
return count;
}
}
// 多线程环境下,最终结果可能小于预期synchronized 关键字
什么是 synchronized
synchronized 是 Java 提供的内置锁机制,用于实现线程同步。它基于**监视器锁(Monitor)**实现,每个对象都有一个监视器锁。
特点:
- 互斥性:同一时刻只有一个线程能执行同步代码
- 可重入性:同一线程可以多次获取同一把锁
- 自动释放:代码执行完或抛异常时自动释放锁
同步方法
public class Counter {
private int count = 0;
// 同步实例方法,锁是 this
public synchronized void increment() {
count++;
}
// 同步静态方法,锁是 Class 对象
public static synchronized void staticMethod() {
// ...
}
}同步代码块
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) { // 指定锁对象
count++;
}
}
public void decrement() {
synchronized (this) { // 使用 this 作为锁
count--;
}
}
}synchronized 原理
锁升级过程:
- 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
Lock 接口
什么是 Lock
Lock 是 Java 5 引入的显式锁接口,提供比 synchronized 更灵活的锁操作。
Lock vs synchronized:
- Lock 需要手动加锁和释放锁
- Lock 支持尝试获取锁、超时获取锁、可中断获取锁
- Lock 可以实现公平锁
- Lock 支持多个条件变量(Condition)
常用实现类:
- ReentrantLock:可重入锁
- ReentrantReadWriteLock:读写锁
- StampedLock:改进的读写锁(Java 8+)
ReentrantLock 基本使用
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中释放锁
}
}
}tryLock() - 尝试获取锁
if (lock.tryLock()) {
try {
// 获取到锁,执行操作
} finally {
lock.unlock();
}
} else {
// 未获取到锁,执行其他逻辑
}
// 带超时的 tryLock
if (lock.tryLock(1, TimeUnit.SECONDS)) {
// ...
}公平锁 vs 非公平锁
// 公平锁:按照请求顺序获取锁
ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁(默认):允许插队,性能更好
ReentrantLock unfairLock = new ReentrantLock(false);Condition 条件变量
public class BoundedBuffer<T> {
private final Object[] items = new Object[100];
private int putIndex, takeIndex, count;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 等待不满
}
items[putIndex] = item;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal(); // 通知不空
} finally {
lock.unlock();
}
}
@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待不空
}
Object item = items[takeIndex];
if (++takeIndex == items.length) takeIndex = 0;
count--;
notFull.signal(); // 通知不满
return (T) item;
} finally {
lock.unlock();
}
}
}ReadWriteLock 读写锁
什么是读写锁
读写锁是一种特殊的锁,将锁分为读锁(共享锁)和写锁(排他锁)。
规则:
- 读-读不互斥:多个线程可以同时持有读锁
- 读-写互斥:读锁和写锁互斥
- 写-写互斥:写锁之间互斥
适用场景:读多写少的场景,可以提高并发性能。
使用示例
适用于读多写少的场景,读锁可以共享,写锁互斥。
public class Cache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public V get(K key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public void put(K key, V value) {
writeLock.lock();
try {
map.put(key, value);
} finally {
writeLock.unlock();
}
}
}volatile 关键字
什么是 volatile
volatile 是 Java 提供的轻量级同步机制,用于保证变量的可见性和有序性,但不保证原子性。
作用:
- 保证可见性:一个线程修改了 volatile 变量,其他线程能立即看到最新值
- 禁止指令重排序:防止编译器和处理器对代码进行重排序优化
- 不保证原子性:
count++这种复合操作不是原子的
底层原理:
- 通过**内存屏障(Memory Barrier)**实现
- 写操作:在写之后插入 Store 屏障,强制刷新到主内存
- 读操作:在读之前插入 Load 屏障,从主内存读取最新值
适用场景:
- 状态标志位
- 双重检查锁定(DCL)
- 单次赋值的场景
可见性
public class VolatileExample {
private volatile boolean running = true;
public void stop() {
running = false; // 修改对其他线程立即可见
}
public void run() {
while (running) {
// 如果不用 volatile,可能永远看不到 running 的变化
}
}
}禁止指令重排
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
// volatile 防止指令重排
// 没有 volatile,可能先赋值再初始化
instance = new Singleton();
}
}
}
return instance;
}
}volatile vs synchronized
| 特性 | volatile | synchronized |
|---|---|---|
| 原子性 | 不保证 | 保证 |
| 可见性 | 保证 | 保证 |
| 有序性 | 保证 | 保证 |
| 阻塞 | 不会 | 会 |
| 适用场景 | 状态标志 | 复合操作 |
原子类
什么是原子类
原子类是 Java 并发包(java.util.concurrent.atomic)提供的线程安全类,基于 CAS(Compare And Swap) 算法实现无锁并发。
CAS 原理:
- CAS 包含三个操作数:内存位置 V、预期值 A、新值 B
- 只有当 V 的值等于 A 时,才会将 V 更新为 B
- 是一种乐观锁机制,不会阻塞线程
常用原子类:
- AtomicInteger/AtomicLong:原子整数
- AtomicBoolean:原子布尔值
- AtomicReference:原子引用
- LongAdder/DoubleAdder:高性能累加器(Java 8+)
- AtomicStampedReference:带版本号的原子引用(解决 ABA 问题)
优点:
- 无锁,性能高
- 避免线程阻塞
缺点:
- 只能保证单个变量的原子性
- 自旋可能消耗 CPU
- 存在 ABA 问题
AtomicInteger
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // ++count
count.getAndIncrement(); // count++
count.addAndGet(5); // count += 5
count.compareAndSet(5, 10); // CAS 操作AtomicReference
AtomicReference<User> userRef = new AtomicReference<>(new User("Tom"));
User oldUser = userRef.get();
User newUser = new User("Jerry");
userRef.compareAndSet(oldUser, newUser);LongAdder(高并发计数器)
// 比 AtomicLong 性能更好,适合高并发写入场景
LongAdder counter = new LongAdder();
counter.increment();
counter.add(10);
long sum = counter.sum();synchronized vs Lock 对比
| 特性 | synchronized | Lock |
|---|---|---|
| 获取锁 | 自动 | 手动 lock() |
| 释放锁 | 自动 | 手动 unlock() |
| 可中断 | 不可 | lockInterruptibly() |
| 超时获取 | 不可 | tryLock(timeout) |
| 公平锁 | 不可 | 可配置 |
| 条件变量 | 单个 | 多个 Condition |
| 性能 | JDK6 后优化,差距不大 | 差距不大 |