锁
目录
锁
速记
synchronized:可重入锁、非公平锁、独占锁
ReentrantLock :可重入锁、可通过传递参数实现公平和非公平锁、独占锁
state为0表示锁空闲,> 0表示锁被占用,且数值代表当前线程的重入次数 。
ReentrantReadWriteLock :可重入锁、可通过传递参数实现公平和非公平锁、读锁(共享锁)、写锁(独占锁)
-
32 位的
state整数一分为二 ,高 16 位管读锁,低 16 位管写锁 -
读锁计数(共享锁数量):共享锁-可重入锁
-
写锁计数(独占锁重入次数):独占锁-可重入锁
┌─────────────────────────┐
│ 锁的类型 │
└─────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
独占锁(排他) 共享锁(共享)
同一时刻只能一个线程持有 同一时刻多个线程可持有
│ │
┌────────┴────────┐ ┌──────┴──────┐
│ │ │ │
可重入独占锁 不可重入独占锁 可重入共享锁 不可重入共享锁
(ReentrantLock) (ThreadPoolExecutor (ReadLock) (极少见)
(synchronized) 的 Worker 锁)
锁(可重入锁)
synchronized:JVM 层面记录锁计数器(monitor 的计数器)
- 可重入锁、非公平锁
ReentrantLock :state 为0 表示锁空闲,> 0 表示锁被占用,且数值代表当前线程的重入次数 。
- 可重入锁
- 可通过传递参数实现公平和非公平锁
- 独占锁
ReentrantReadWriteLock : 32 位的 state 整数一分为二 ,高 16 位管读锁,低 16 位管写锁
-
可通过传递参数实现公平和非公平锁
-
读锁计数(共享锁数量):共享锁
-
写锁计数(独占锁重入次数):独占锁-可重入锁
-
问题:用读写锁时,我读的时候正在写,怎么保证正确性的??? 解答:读写锁通过加锁时的exclusiveCount(state)检查,保证读操作在写锁释放前绝不会被授予;通过 volatile 的可见性语义,保证写操作的结果对后续读操作立即可见。
为什么 99% 的锁设计成“可重入”?
| 原因 | 说明 |
|---|---|
| 避免死锁 | 如果一个同步方法调用另一个同步方法(同一个锁),不可重入就会导致死锁 |
| 更符合直觉 | 程序员通常认为“我已经拥有这把锁,再次进入应该没问题” |
| 简化编程 | 不用在每个递归或嵌套调用中反复判断是否已持有锁 |
锁(不可重入)
1. 最经典的不可重入锁:ThreadPoolExecutor 内部的 ReentrantLock 变种
在 ThreadPoolExecutor 中,有一个内部类 Worker,它继承自 AbstractQueuedSynchronizer,实现了一个不可重入的独占锁:
// ThreadPoolExecutor.Worker 内部类(简化)
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
// 尝试获取锁:如果 state == 0 则 CAS 获取,否则失败
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false; // ★ 如果已经持有,直接返回 false,不允许重入
}
// 释放锁:只有持有者才能释放
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 外部调用的 lock 方法
public void lock() {
acquire(1);
}
}
为什么 Worker 要设计成不可重入?
- Worker 代表一个工作线程,它在执行任务前会
lock()。 - 如果任务中又调用了
execute()或submit()提交新任务,并尝试获取同一个 Worker 的锁,一旦可重入,就可能导致任务循环依赖或资源耗尽。 - 设计成不可重入,可以快速暴露这种错误逻辑(第二次获取直接失败),而不是让线程死锁或无限等待。
2. 手动实现不可重入锁(基于 AQS)
你可以轻松自己实现一个不可重入锁:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class NonReentrantLock {
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int acquires) {
// ★ 如果 state == 0,则获取锁;如果 > 0,直接失败
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false; // 不允许重入
}
@Override
protected boolean tryRelease(int releases) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
测试一下:
NonReentrantLock lock = new NonReentrantLock();
lock.lock();
System.out.println("第一次获取成功");
lock.lock(); // ★ 这里会阻塞,因为 state == 1,tryAcquire 失败!
System.out.println("第二次获取成功(不会执行到这里)");
浙公网安备 33010602011771号