【多线程同步机制】
深入剖析 synchronized 的锁升级过程、ReentrantLock 的 AQS 实现原理、CountDownLatch 与 CyclicBarrier 的区别,并结合 ThreadLocal 解决线程安全问题。
💡 摘要:你是否遇到过
i++
在多线程下结果不正确?
是否在synchronized
和ReentrantLock
之间犹豫不决?
Java 多线程同步机制远不止synchronized
,
从 JVM 内置锁 到 JUC 并发包,
从 悲观锁 到 乐观锁,
从volatile
内存语义 到CAS
无锁算法,
本文将带你系统梳理 Java 同步的 8 大机制,
深入剖析synchronized
的锁升级过程、ReentrantLock
的 AQS 实现原理、CountDownLatch
与CyclicBarrier
的区别,
并结合ThreadLocal
解决线程安全问题。
文末附同步机制选型决策树与面试高频问题,
助你彻底掌握并发编程的核心武器。
一、为什么需要线程同步?
多线程环境下,共享资源的并发访问会导致数据不一致。
经典问题:i++
的线程安全
public class Counter {
private int i = 0;
public void increment() {
i++; // 非原子操作!
}
public int get() {
return i;
}
}
// 多线程调用
Counter counter = new Counter();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(counter::increment);
}
executor.shutdown();
// 最终结果很可能 < 1000!
❌
i++
为什么不是原子的?
它包含 3 个步骤:读取i
→i+1
→ 写回i
。
多个线程可能同时读取到相同的i
值,导致丢失更新。
二、Java 同步机制概览
机制 | 类型 | 特点 | 适用场景 |
---|---|---|---|
1. synchronized 关键字 |
悲观锁 | JVM 内置,简单 | 方法/代码块同步 |
2. ReentrantLock |
悲观锁 | 功能丰富,可中断 | 高级锁控制 |
3. volatile 关键字 |
内存可见性 | 轻量级,无锁 | 状态标志、单例 |
4. Atomic 类 (CAS) |
乐观锁 | 无锁,高性能 | 计数器、状态更新 |
5. ThreadLocal |
线程隔离 | 每线程副本 | 数据库连接、用户信息 |
6. 显式 Lock 配合 Condition |
条件等待 | 精确唤醒 | 生产者-消费者 |
7. 并发集合 (ConcurrentHashMap) | 安全容器 | 分段锁/CAS | 高并发读写 |
8. 同步工具类 (CountDownLatch) | 协调线程 | 屏障、计数 | 线程协作 |
1. synchronized
关键字(JVM 内置锁)
✅ 语法
// 1. 修饰实例方法:锁当前实例 (this)
public synchronized void method1() { /* ... */ }
// 2. 修饰静态方法:锁类对象 (Counter.class)
public static synchronized void method2() { /* ... */ }
// 3. 修饰代码块:锁指定对象
public void method3() {
synchronized (this) { // 或 synchronized (lockObject)
// 同步代码
}
}
✅ 特性
- 可重入:同一线程可多次获取同一把锁。
- 自动释放:异常时也会自动释放锁。
- 非公平锁:不保证等待线程的获取顺序。
🔬 synchronized
的锁升级(JDK 1.6+ 优化)
为了减少重量级锁的开销,JVM 实现了锁升级:
- 偏向锁:偏向第一个获取锁的线程,后续该线程进入同步块无需再同步。
- 轻量级锁:当有第二个线程竞争时,升级为轻量级锁(自旋)。
- 重量级锁:竞争激烈时,升级为操作系统互斥量(Mutex),线程阻塞。
✅ 优点:简单、安全、JVM 优化好。
⚠️ 缺点:不可中断、不支持超时、不支持条件变量。
2. ReentrantLock
(可重入锁)
✅ 语法
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获取锁
try {
// 同步代码
} finally {
lock.unlock(); // 必须在 finally 中释放!
}
}
✅ 优势(相比 synchronized
)
- 可中断:
lockInterruptibly()
可响应中断。 - 可超时:
tryLock(timeout)
尝试获取,超时失败。 - 公平锁:
new ReentrantLock(true)
可设置为公平锁。 - 多个条件变量:配合
Condition
实现精确唤醒。
✅ 示例:公平锁与超时
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
public void timedOperation() throws InterruptedException {
if (fairLock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 操作
} finally {
fairLock.unlock();
}
} else {
System.out.println("获取锁超时");
}
}
⚠️ 注意:必须在
finally
块中调用unlock()
,否则可能导致死锁。
3. volatile
关键字(内存可见性)
✅ 作用
- 保证可见性:一个线程修改了
volatile
变量,其他线程能立即看到。 - 禁止指令重排序:保证代码执行顺序。
✅ 适用场景
- 状态标志:控制线程的生命周期。
private volatile boolean running = true;
public void run() {
while (running) {
// 执行任务
}
}
public void shutdown() {
running = false; // 其他线程可立即看到
}
- 双重检查锁定(DCL)单例
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
⚠️ 注意:
volatile
不保证原子性!volatile int i; i++
仍然是线程不安全的。
4. Atomic
类(CAS 无锁算法)
✅ 核心:CAS (Compare-And-Swap)
AtomicInteger
,AtomicLong
,AtomicReference
等。- 基于 CPU 的
cmpxchg
指令实现。 - 使用
Unsafe
类进行原子操作。
✅ 示例
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // 原子自增
}
public int get() {
return counter.get();
}
✅ ABA 问题与 AtomicStampedReference
CAS 可能出现 ABA 问题:值从 A→B→A,CAS 认为未变,但实际已变。
// 使用版本号解决 ABA
AtomicStampedReference<Integer> stampedRef =
new AtomicStampedReference<>(100, 0);
int[] stampHolder = new int[1];
Integer oldVal = stampedRef.get(stampHolder);
int stamp = stampHolder[0];
// 带版本号的 CAS
boolean success = stampedRef.compareAndSet(oldVal, newVal, stamp, stamp + 1);
✅ 优点:无锁,性能高,适合高并发计数。
⚠️ 缺点:高竞争下可能“自旋”消耗 CPU。
5. ThreadLocal
(线程局部变量)
✅ 作用
为每个线程提供独立的变量副本,避免共享。
✅ 示例:数据库连接管理
public class ConnectionHolder {
private static final ThreadLocal<Connection> connectionHolder =
new ThreadLocal<Connection>() {
@Override
protected Connection initialValue() {
return DriverManager.getConnection(URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
public static void remove() {
connectionHolder.remove(); // 防止内存泄漏!
}
}
// 在同一线程中,多次调用 getConnection() 返回同一个连接
Connection conn1 = ConnectionHolder.getConnection();
Connection conn2 = ConnectionHolder.getConnection(); // == conn1
⚠️ 重要:使用完后务必调用
remove()
,否则在线程池环境下可能导致内存泄漏。
6. Condition
(条件变量)
✅ 作用
配合 ReentrantLock
,实现精确的线程等待与唤醒。
✅ 示例:生产者-消费者
public class BoundedBuffer {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[100];
private int putIndex, takeIndex, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 等待不满
}
items[putIndex] = x;
if (++putIndex == items.length) putIndex = 0;
++count;
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待不空
}
Object x = items[takeIndex];
if (++takeIndex == items.length) takeIndex = 0;
--count;
notFull.signal(); // 唤醒生产者
return x;
} finally {
lock.unlock();
}
}
}
✅ 优势:比
Object.wait()/notify()
更灵活,可创建多个Condition
。
7. 并发集合
✅ ConcurrentHashMap
- 线程安全的
HashMap
。 - JDK 1.8+ 使用
CAS + synchronized
,锁粒度更细。 - 读操作不加锁,写操作锁桶。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
Integer value = map.get("key");
✅ CopyOnWriteArrayList
- 写时复制,读操作无锁。
- 适合读多写少的场景。
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item");
list.get(0);
8. 同步工具类
✅ CountDownLatch
(倒计时门闩)
- 等待一组操作完成。
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
// 执行任务
latch.countDown(); // 计数减一
});
}
latch.await(); // 等待计数为0
System.out.println("所有任务完成!");
✅ CyclicBarrier
(循环屏障)
- 让一组线程互相等待,到达屏障点后一起继续。
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程已到达屏障,开始下一阶段");
});
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
// 阶段1
barrier.await(); // 等待其他线程
// 阶段2
});
}
✅ Semaphore
(信号量)
- 控制同时访问特定资源的线程数量。
Semaphore semaphore = new Semaphore(3); // 同时允许3个线程
semaphore.acquire(); // 获取许可
try {
// 访问资源
} finally {
semaphore.release(); // 释放许可
}
三、同步机制选型决策树
四、面试高频问题
❓1. synchronized
和 ReentrantLock
的区别?
答:
synchronized
:JVM 内置,简单,自动释放,非公平。ReentrantLock
:API 级,功能丰富(可中断、超时、公平锁),需手动释放。
❓2. volatile
能保证原子性吗?
答:不能。
volatile
只保证可见性和禁止重排序。i++
这样的复合操作仍需synchronized
或AtomicInteger
。
❓3. 什么是 CAS?有什么缺点?
答:
- CAS:Compare-And-Swap,乐观锁基础。
- 缺点:
- ABA 问题(可用
AtomicStampedReference
解决)- 自旋消耗 CPU(高竞争下)
- 只能保证单个变量的原子性
❓4. ThreadLocal
会导致内存泄漏吗?
答:可能。
ThreadLocal
的Entry
是WeakReference
,但值是强引用。
如果ThreadLocal
实例被回收,但线程(如线程池中的线程)长期运行,Entry
的键为null
,但值仍存在,导致内存泄漏。
解决:使用完后调用remove()
。
❓5. synchronized
锁升级的过程?
答:
- 无锁
- 偏向锁:偏向第一个线程。
- 轻量级锁:自旋,避免阻塞。
- 重量级锁:阻塞,依赖操作系统。
五、总结
机制 | 推荐度 | 说明 |
---|---|---|
synchronized |
✅ | 简单场景首选 |
ReentrantLock |
✅✅ | 需要高级功能时 |
volatile |
✅ | 状态标志、DCL |
Atomic 类 |
✅✅ | 高并发计数 |
ThreadLocal |
✅ | 线程隔离 |
Condition |
✅ | 精确唤醒 |
并发集合 | ✅✅ | 高并发容器 |
同步工具类 | ✅ | 线程协作 |
✅ 终极建议:
- 优先使用
synchronized
,简单有效。- 高并发计数用
Atomic
。- 状态标志用
volatile
。- 复杂协作用
ReentrantLock + Condition
。- 避免过度同步,考虑并发集合和无锁算法。
掌握这些同步机制,你就能从容应对 Java 并发编程的挑战!
更多推荐
所有评论(0)