【Java并发】synchronized与ReentrantLock深入解析
synchronized和ReentrantLock是Java中最常用的两种锁机制。本文深入分析它们的底层原理、使用方法和适用场景。
synchronized关键字
synchronized是Java的内置锁,也叫监视器锁(Monitor Lock)或内置监视器。
使用方式
1. 修饰实例方法
1 2 3 4 5 6 7 8 9 10 11 12
| public class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; } }
|
2. 修饰静态方法
1 2 3 4 5 6 7 8
| public class Counter { private static int count = 0;
public static synchronized void increment() { count++; } }
|
3. 同步代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Counter { private int count = 0; private final Object lock = new Object();
public void increment() { synchronized (lock) { count++; } }
public void decrement() { synchronized (this) { count--; } }
public static void staticMethod() { synchronized (Counter.class) { } } }
|
底层原理:Monitor
每个Java对象都关联一个Monitor(监视器),synchronized就是通过Monitor实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ┌─────────────────────────────────────────────┐ │ Monitor │ ├─────────────────────────────────────────────┤ │ Owner: 当前持有锁的线程 │ │ │ │ EntryList: 等待获取锁的线程队列 │ │ ┌────┐ ┌────┐ ┌────┐ │ │ │ T1 │ │ T2 │ │ T3 │ 阻塞等待 │ │ └────┘ └────┘ └────┘ │ │ │ │ WaitSet: 调用wait()后等待的线程 │ │ ┌────┐ ┌────┐ │ │ │ T4 │ │ T5 │ 等待被notify唤醒 │ │ └────┘ └────┘ │ │ │ │ count: 重入计数 │ └─────────────────────────────────────────────┘
|
字节码层面
1 2 3 4 5
| public void method() { synchronized (this) { } }
|
编译后的字节码:
1 2 3 4 5
| monitorenter // 获取锁,count+1 // 临界区代码 monitorexit // 释放锁,count-1 // 异常处理 monitorexit // 确保异常时也释放锁
|
对象头与锁标记
Java对象在内存中的布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────┐ │ 对象头 │ ├───────────────────────────┬─────────────────────────────┤ │ Mark Word │ Class Pointer │ │ (锁信息、GC等) │ (指向类元数据) │ ├───────────────────────────┴─────────────────────────────┤ │ 实例数据 │ ├─────────────────────────────────────────────────────────┤ │ 对齐填充 │ └─────────────────────────────────────────────────────────┘
Mark Word (64位JVM): ┌────────────────────────────────────────────────────────┐ │ 锁状态 │ 25bit │ 31bit │ 1bit │ 4bit │ 1bit │ 2bit │ ├──────────┼────────┼────────────┼───────┼──────┼───────┼──────┤ │ 无锁 │ unused │ hashcode │ unused│ age │ biased│ 01 │ │ 偏向锁 │ threadID (54bit) │ epoch │ age │ biased│ 01 │ │ 轻量级锁 │ 指向栈中锁记录的指针 (62bit) │ 00 │ │ 重量级锁 │ 指向Monitor的指针 (62bit) │ 10 │ │ GC标记 │ (62bit) │ 11 │ └────────────────────────────────────────────────────────┘
|
锁升级过程
JDK 6之后,synchronized引入了锁升级机制,从偏向锁→轻量级锁→重量级锁:
1 2 3 4 5 6 7 8 9 10 11 12 13
| ┌─────────────────────────────────────────────────────────────────┐ │ 锁升级过程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 无锁 ──▶ 偏向锁 ──▶ 轻量级锁 ──▶ 重量级锁 │ │ │ │ │ │ │ │ │ │ │ │ 单线程访问 有竞争但 竞争激烈 │ │ 无竞争 交替执行 同时竞争 │ │ │ │ 升级是单向的,不会降级(偏向锁例外) │ │ │ └─────────────────────────────────────────────────────────────────┘
|
1. 偏向锁(Biased Locking)
适用场景:只有一个线程访问同步块。
1 2 3 4 5 6 7 8 9 10
| 线程A第一次获取锁: ┌─────────────────┐ │ Mark Word │ │ ThreadID = A │ 记录线程A的ID │ biased = 1 │ │ lock = 01 │ └─────────────────┘
线程A再次获取锁: 检查ThreadID == A?是 → 直接进入(无需CAS)
|
偏向锁只需要在第一次获取时进行CAS操作,之后同一线程进入同步块只需检查ThreadID,几乎无开销。
2. 轻量级锁(Lightweight Locking)
适用场景:多个线程交替访问同步块(无实际竞争)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 线程A获取轻量级锁:
1. 在栈帧中创建Lock Record ┌──────────────────┐ │ Lock Record │ │ ┌──────────────┐ │ │ │Displaced Mark│ │ ← 保存对象原来的Mark Word │ │ Word │ │ │ └──────────────┘ │ │ ┌──────────────┐ │ │ │ owner │─┼──▶ 指向锁对象 │ └──────────────┘ │ └──────────────────┘
2. CAS将对象Mark Word替换为指向Lock Record的指针 ┌─────────────────┐ │ Mark Word │ │ ptr to LockRec │──▶ 指向线程A栈中的Lock Record │ lock = 00 │ └─────────────────┘
3. 释放锁时,CAS将Displaced Mark Word还原
|
3. 重量级锁(Heavyweight Locking)
适用场景:多个线程同时竞争锁。
1 2 3 4 5 6 7 8 9
| 轻量级锁CAS失败(有竞争)→ 膨胀为重量级锁
┌─────────────────┐ │ Mark Word │ │ ptr to Monitor │──▶ 指向操作系统的Monitor对象 │ lock = 10 │ └─────────────────┘
重量级锁涉及操作系统的Mutex Lock,需要用户态到内核态切换,开销大
|
锁升级图解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ┌─────────────┐ │ 无锁 │ └──────┬──────┘ │ 第一个线程访问 ▼ ┌─────────────┐ │ 偏向锁 │ ← 记录线程ID,之后无需CAS └──────┬──────┘ │ 出现第二个线程竞争 ▼ ┌─────────────┐ │ 轻量级锁 │ ← CAS自旋尝试获取 └──────┬──────┘ │ 自旋失败/竞争激烈 ▼ ┌─────────────┐ │ 重量级锁 │ ← 阻塞等待,涉及内核态切换 └─────────────┘
|
wait/notify机制
synchronized配合Object的wait/notify实现等待通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ProducerConsumer { private final Queue<Integer> queue = new LinkedList<>(); private final int capacity = 10;
public synchronized void produce(int item) throws InterruptedException { while (queue.size() == capacity) { wait(); } queue.add(item); notifyAll(); }
public synchronized int consume() throws InterruptedException { while (queue.isEmpty()) { wait(); } int item = queue.poll(); notifyAll(); return item; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| wait(): 1. 释放锁 2. 进入WaitSet等待 3. 被notify唤醒后重新竞争锁
notify(): 1. 从WaitSet中唤醒一个线程 2. 被唤醒线程进入EntryList竞争锁
notifyAll(): 1. 唤醒WaitSet中所有线程 2. 所有被唤醒线程进入EntryList竞争锁
|
ReentrantLock
ReentrantLock是java.util.concurrent包提供的可重入锁,功能比synchronized更丰富。
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock();
public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } }
public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }
|
底层原理:AQS
ReentrantLock基于AQS(AbstractQueuedSynchronizer)实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ┌─────────────────────────────────────────────────────────┐ │ AQS │ ├─────────────────────────────────────────────────────────┤ │ state: int │ │ ├── 0: 锁空闲 │ │ └── >0: 锁被持有(值为重入次数) │ │ │ │ exclusiveOwnerThread: 持有锁的线程 │ │ │ │ CLH队列(双向链表):等待获取锁的线程 │ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │ head │◀──▶│ Node │◀──▶│ Node │◀──▶│ tail │ │ │ │(哨兵)│ │ T1 │ │ T2 │ │ T3 │ │ │ └──────┘ └──────┘ └──────┘ └──────┘ │ │ │ └─────────────────────────────────────────────────────────┘
|
加锁过程(非公平锁)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void lock() { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(currentThread); return; }
acquire(1); }
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); } }
|
加锁流程图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| lock() │ ▼ CAS(state: 0→1)成功? ──是──▶ 获得锁,设置owner │ 否 │ ▼ tryAcquire() │ ├─▶ state==0?CAS抢锁 │ └─▶ owner==当前线程?state++(重入) │ 失败 │ ▼ addWaiter() 加入CLH队列 │ ▼ acquireQueued() 自旋+阻塞 │ ├─▶ 前驱是head?尝试获取锁 │ └─▶ 不是head?park()阻塞等待
|
公平锁 vs 非公平锁
1 2 3 4 5 6
| ReentrantLock unfairLock = new ReentrantLock(); ReentrantLock unfairLock = new ReentrantLock(false);
ReentrantLock fairLock = new ReentrantLock(true);
|
非公平锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } return false; }
|
1 2 3 4 5 6 7 8 9 10 11
| 非公平锁:新线程可以插队
队列:head ◀──▶ T1 ◀──▶ T2 ◀──▶ T3
T4到来时: 1. 先CAS尝试获取锁 2. 如果锁刚好释放,T4可能抢到(插队) 3. CAS失败才排队
优点:吞吐量高(减少线程切换) 缺点:可能导致饥饿(T1一直抢不到)
|
公平锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } return false; }
|
1 2 3 4 5 6 7 8 9 10 11
| 公平锁:严格按顺序获取
队列:head ◀──▶ T1 ◀──▶ T2 ◀──▶ T3
T4到来时: 1. 检查队列是否有等待线程 2. 有等待线程,直接排队 3. 只有队首线程才能获取锁
优点:不会饥饿 缺点:吞吐量低(频繁线程切换)
|
可中断锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ReentrantLock lock = new ReentrantLock();
Thread t = new Thread(() -> { try { lock.lockInterruptibly(); try { } finally { lock.unlock(); } } catch (InterruptedException e) { System.out.println("获取锁时被中断"); } });
t.start(); Thread.sleep(1000); t.interrupt();
|
超时锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ReentrantLock lock = new ReentrantLock();
public void method() { boolean acquired = false; try { acquired = lock.tryLock(2, TimeUnit.SECONDS); if (acquired) { } else { } } catch (InterruptedException e) { } finally { if (acquired) { lock.unlock(); } } }
if (lock.tryLock()) { try { } finally { lock.unlock(); } } else { }
|
Condition条件变量
Condition是synchronized中wait/notify的替代品,功能更强大。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class BoundedBuffer<T> { private final Object[] items; private int putIndex, takeIndex, count;
private final ReentrantLock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition();
public BoundedBuffer(int capacity) { items = new Object[capacity]; }
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(); } T item = (T) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; notFull.signal(); return item; } finally { lock.unlock(); } } }
|
Condition vs wait/notify
| 特性 |
wait/notify |
Condition |
| 条件数量 |
1个(对象的WaitSet) |
多个(可创建多个Condition) |
| 等待 |
wait() |
await() |
| 唤醒一个 |
notify() |
signal() |
| 唤醒所有 |
notifyAll() |
signalAll() |
| 超时等待 |
wait(timeout) |
await(time, unit) |
| 截止时间 |
不支持 |
awaitUntil(deadline) |
| 不可中断 |
不支持 |
awaitUninterruptibly() |
读写锁 ReentrantReadWriteLock
适用于读多写少的场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class Cache<K, V> { private final Map<K, V> map = new HashMap<>(); private final ReentrantReadWriteLock 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(); } }
public V computeIfAbsent(K key, Function<K, V> loader) { readLock.lock(); try { V value = map.get(key); if (value != null) { return value; } } finally { readLock.unlock(); }
writeLock.lock(); try { V value = map.get(key); if (value == null) { value = loader.apply(key); map.put(key, value); } return value; } finally { writeLock.unlock(); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| 读写锁规则: ┌─────────────┬──────────────┬──────────────┐ │ │ 读锁请求 │ 写锁请求 │ ├─────────────┼──────────────┼──────────────┤ │ 无锁 │ 允许 │ 允许 │ │ 持有读锁 │ 允许(共享) │ 阻塞 │ │ 持有写锁 │ 阻塞 │ 阻塞(独占) │ └─────────────┴──────────────┴──────────────┘
读-读:不互斥 读-写:互斥 写-写:互斥
|
synchronized vs ReentrantLock
功能对比
| 特性 |
synchronized |
ReentrantLock |
| 使用方式 |
关键字 |
API调用 |
| 自动释放 |
是(退出同步块自动释放) |
否(必须手动unlock) |
| 可中断 |
否 |
是(lockInterruptibly) |
| 超时获取 |
否 |
是(tryLock(timeout)) |
| 非阻塞获取 |
否 |
是(tryLock()) |
| 公平锁 |
否(只有非公平) |
可选(构造参数) |
| 条件变量 |
1个 |
多个(newCondition) |
| 可重入 |
是 |
是 |
| 锁升级 |
是 |
否 |
性能对比
JDK 6之前,synchronized性能较差。JDK 6引入锁升级后,两者性能相近:
1 2 3 4 5
| 低竞争场景: synchronized ≈ ReentrantLock(偏向锁/轻量级锁优化)
高竞争场景: 两者都会退化为重量级锁/阻塞,性能相近
|
使用建议
1 2 3 4 5 6 7 8 9 10
|
public synchronized void simpleMethod() { }
synchronized (lock) { }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
lock.lockInterruptibly();
if (lock.tryLock(1, TimeUnit.SECONDS)) { }
new ReentrantLock(true);
Condition notFull = lock.newCondition(); Condition notEmpty = lock.newCondition();
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
|
最佳实践
1. 减小锁粒度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public synchronized void process() { readFromDB(); calculate(); writeToFile(); }
public void process() { Data data = readFromDB(); synchronized (this) { calculate(data); } writeToFile(data); }
|
2. 避免死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| void method1() { synchronized (lockA) { synchronized (lockB) { } } }
void method2() { synchronized (lockB) { synchronized (lockA) { } } }
void method1() { synchronized (lockA) { synchronized (lockB) { } } }
void method2() { synchronized (lockA) { synchronized (lockB) { } } }
boolean acquired = false; try { acquired = lock1.tryLock(1, TimeUnit.SECONDS) && lock2.tryLock(1, TimeUnit.SECONDS); if (acquired) { } } finally { if (acquired) { lock2.unlock(); lock1.unlock(); } }
|
3. 使用try-finally确保释放
1 2 3 4 5 6 7 8
| ReentrantLock lock = new ReentrantLock();
lock.lock(); try { } finally { lock.unlock(); }
|
4. 虚拟线程中使用ReentrantLock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
synchronized (lock) { Thread.sleep(1000); }
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { Thread.sleep(1000); } finally { lock.unlock(); }
|
总结
| 维度 |
synchronized |
ReentrantLock |
| 实现层面 |
JVM内置,字节码层面 |
JDK类库,AQS实现 |
| 锁类型 |
非公平 |
公平/非公平可选 |
| 中断支持 |
不支持 |
支持 |
| 超时支持 |
不支持 |
支持 |
| 条件变量 |
单一 |
多个 |
| 锁优化 |
偏向锁→轻量级→重量级 |
无(始终AQS队列) |
| 使用复杂度 |
简单 |
稍复杂 |
| 释放安全 |
自动释放 |
需手动释放 |
选择建议:
- 简单场景:优先synchronized,简洁不易出错
- 需要高级功能:使用ReentrantLock
- JDK 21虚拟线程:优先ReentrantLock
- 读多写少:使用ReentrantReadWriteLock