第一章:ReentrantLock 入门
1. 为什么需要ReentrantLock
在Java早期版本中,synchronized是唯一的同步机制。然而,随着并发编程需求的日益复杂,synchronized的局限性逐渐显现,JDK 1.5引入了java.util.concurrent.locks包,其中ReentrantLock成为了最重要的显式锁实现。
1.1 synchronized的局限性
synchronized是Java内置的同步关键字,使用简单,但存在以下局限:
java
/**
* synchronized的局限性演示
*/
public class SynchronizedLimitations {
private final Object lock = new Object();
/**
* 局限1:无法中断等待
* 线程在等待synchronized锁时,无法响应中断
*/
public void cannotInterrupt() {
synchronized (lock) {
// 如果其他线程持有锁很长时间,当前线程只能一直等待
// 即使调用了 thread.interrupt(),也无法中断等待
}
}
/**
* 局限2:无法设置超时
* 必须无限期等待,直到获取到锁
*/
public void cannotTimeout() {
synchronized (lock) {
// 无法指定"如果3秒内获取不到锁就放弃"
}
}
/**
* 局限3:无法尝试获取
* 不能非阻塞地尝试获取锁
*/
public void cannotTryLock() {
// 无法实现:如果锁可用就获取,否则立即返回
synchronized (lock) {
// ...
}
}
/**
* 局限4:只支持单一条件
* Object的wait/notify只能有一个等待集合
*/
public void singleCondition() throws InterruptedException {
synchronized (lock) {
// 只能调用 lock.wait(),所有等待线程共用一个队列
// 无法区分"等待空间"和"等待数据"的线程
lock.wait();
}
}
/**
* 局限5:无法查询锁状态
* 不知道锁是否被其他线程持有
*/
public void cannotQueryState() {
// 无法判断锁是否被占用
// 无法知道当前线程的重入次数
}
}
1.2 ReentrantLock的优势
ReentrantLock完美解决了上述所有问题:

1.3 什么时候选择ReentrantLock
| 场景 | 推荐选择 | 原因 |
|---|---|---|
| 简单同步,性能要求不高 | synchronized | 语法简单,自动释放 |
| 需要可中断等待 | ReentrantLock | lockInterruptibly() |
| 需要超时获取 | ReentrantLock | tryLock(timeout) |
| 需要公平锁 | ReentrantLock | new ReentrantLock(true) |
| 需要多个等待条件 | ReentrantLock | newCondition() |
| 需要查询锁状态 | ReentrantLock | isLocked()等方法 |
| 锁粒度需要更细 | ReentrantLock | 更灵活的加锁/解锁控制 |
2. ReentrantLock核心概念
2.1 什么是ReentrantLock
ReentrantLock是一个可重入的互斥锁(Reentrant Mutual Exclusion Lock)。让我们拆解这个定义:
- 可重入(Reentrant):同一个线程可以多次获取同一把锁,不会造成死锁
- 互斥(Mutual Exclusion):同一时刻只有一个线程能持有锁
- 锁(Lock):用于控制对共享资源的访问
java
/**
* 可重入特性演示
*/
public class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void outer() {
lock.lock(); // 第一次获取锁,holdCount = 1
try {
System.out.println("outer方法获取锁");
inner(); // 调用内部方法,再次获取同一把锁
} finally {
lock.unlock(); // 释放锁,holdCount = 0
}
}
public void inner() {
lock.lock(); // 第二次获取锁,holdCount = 2(可重入!)
try {
System.out.println("inner方法获取锁");
} finally {
lock.unlock(); // 释放锁,holdCount = 1
}
}
public static void main(String[] args) {
ReentrantDemo demo = new ReentrantDemo();
demo.outer(); // 不会死锁!
}
}
2.2 公平锁与非公平锁
ReentrantLock支持两种获取锁的策略

非公平锁(默认):
- 新线程可以"插队",直接尝试获取锁
- 优点:吞吐量高,减少线程切换开销
- 缺点:可能导致某些线程"饥饿"(长期等待)
公平锁:
- 严格按照先来后到的顺序获取锁
- 优点:所有线程都能公平获得执行机会
- 缺点:吞吐量较低,需要频繁的线程切换
java
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();
ReentrantLock unfairLock2 = new ReentrantLock(false);
// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);
2.3 类结构概览

3. 基础API详解
3.1 API总览
| 方法 | 说明 | 阻塞 | 可中断 | 可超时 |
|---|---|---|---|---|
lock() |
获取锁 | ✅ | ❌ | ❌ |
lockInterruptibly() |
可中断地获取锁 | ✅ | ✅ | ❌ |
tryLock() |
尝试获取锁(非阻塞) | ❌ | - | - |
tryLock(time, unit) |
超时获取锁 | ✅ | ✅ | ✅ |
unlock() |
释放锁 | - | - | - |
newCondition() |
创建条件变量 | - | - | - |
3.2 lock() - 获取锁
lock()是最基本的获取锁方法。如果锁被其他线程持有,当前线程会阻塞等待,直到获取到锁。
java
/**
* lock()方法使用示例
*/
public class LockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
/**
* 标准使用模式:lock-try-finally-unlock
*/
public void increment() {
lock.lock(); // 获取锁
try {
count++; // 临界区代码
} finally {
lock.unlock(); // 必须在finally中释放锁!
}
}
/**
* 错误示例:没有在finally中释放锁
*/
public void wrongUsage() {
lock.lock();
count++;
lock.unlock(); // 如果count++抛异常,锁永远不会释放!
}
}
重要警告:
lock()和unlock()必须配对使用,且unlock()必须放在finally块中,确保无论是否发生异常都能释放锁。
3.3 lockInterruptibly() - 可中断获取锁
当线程在等待锁时,可以响应中断。这在需要取消长时间等待的场景非常有用。
java
/**
* lockInterruptibly()使用示例
*/
public class InterruptibleLockExample {
private final ReentrantLock lock = new ReentrantLock();
/**
* 可中断的获取锁
*/
public void interruptibleMethod() throws InterruptedException {
// 如果线程被中断,会抛出InterruptedException
lock.lockInterruptibly();
try {
// 执行业务逻辑
doSomething();
} finally {
lock.unlock();
}
}
/**
* 实际应用:可取消的任务
*/
public void cancellableTask() {
Thread worker = new Thread(() -> {
try {
lock.lockInterruptibly();
try {
// 长时间运行的任务
while (!Thread.currentThread().isInterrupted()) {
processData();
}
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("任务被取消");
Thread.currentThread().interrupt(); // 恢复中断状态
}
});
worker.start();
// 需要取消时
// worker.interrupt(); // 会使lockInterruptibly()抛出异常
}
private void doSomething() { /* ... */ }
private void processData() { /* ... */ }
}
3.4 tryLock() - 尝试获取锁
tryLock()是非阻塞的获取锁方法。如果锁可用,立即获取并返回true;否则立即返回false。
java
/**
* tryLock()使用示例
*/
public class TryLockExample {
private final ReentrantLock lock = new ReentrantLock();
/**
* 非阻塞获取锁
*/
public boolean tryProcess() {
if (lock.tryLock()) { // 尝试获取锁
try {
// 获取成功,执行业务逻辑
doProcess();
return true;
} finally {
lock.unlock();
}
} else {
// 获取失败,执行降级逻辑
System.out.println("锁被占用,执行降级操作");
return false;
}
}
/**
* 实际应用:避免死锁的转账操作
*/
public boolean transfer(Account from, Account to, int amount) {
// 尝试同时获取两把锁,避免死锁
while (true) {
if (from.lock.tryLock()) {
try {
if (to.lock.tryLock()) {
try {
// 两把锁都获取成功,执行转账
from.debit(amount);
to.credit(amount);
return true;
} finally {
to.lock.unlock();
}
}
} finally {
from.lock.unlock();
}
}
// 获取失败,短暂休眠后重试
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
private void doProcess() { /* ... */ }
}
class Account {
final ReentrantLock lock = new ReentrantLock();
private int balance;
void debit(int amount) { balance -= amount; }
void credit(int amount) { balance += amount; }
}
3.5 tryLock(long time, TimeUnit unit) - 超时获取锁
在指定时间内尝试获取锁。如果在超时时间内获取到锁,返回true;否则返回false。
java
/**
* tryLock(timeout)使用示例
*/
public class TimeoutLockExample {
private final ReentrantLock lock = new ReentrantLock();
/**
* 超时获取锁
*/
public boolean processWithTimeout() {
try {
// 尝试在3秒内获取锁
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
doProcess();
return true;
} finally {
lock.unlock();
}
} else {
// 超时未获取到锁
System.out.println("获取锁超时");
return false;
}
} catch (InterruptedException e) {
// 等待过程中被中断
System.out.println("等待锁时被中断");
Thread.currentThread().interrupt();
return false;
}
}
/**
* 实际应用:带超时的分布式锁模拟
*/
public boolean executeWithLock(Runnable task, long timeout, TimeUnit unit) {
try {
if (lock.tryLock(timeout, unit)) {
try {
task.run();
return true;
} finally {
lock.unlock();
}
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
private void doProcess() { /* ... */ }
}
3.6 unlock() - 释放锁
释放锁,将锁的持有计数减1。当计数变为0时,锁完全释放,其他等待线程可以竞争获取。
java
/**
* unlock()使用示例
*/
public class UnlockExample {
private final ReentrantLock lock = new ReentrantLock();
/**
* 可重入场景下的unlock
*/
public void reentrantExample() {
lock.lock(); // holdCount = 1
try {
System.out.println("外层锁定,holdCount = " + lock.getHoldCount());
lock.lock(); // holdCount = 2
try {
System.out.println("内层锁定,holdCount = " + lock.getHoldCount());
} finally {
lock.unlock(); // holdCount = 1
System.out.println("内层释放,holdCount = " + lock.getHoldCount());
}
} finally {
lock.unlock(); // holdCount = 0,锁完全释放
System.out.println("外层释放,holdCount = " + lock.getHoldCount());
}
}
/**
* 错误:非锁持有者调用unlock
*/
public void wrongUnlock() {
// 没有获取锁就调用unlock,会抛出IllegalMonitorStateException
try {
lock.unlock(); // 抛出异常!
} catch (IllegalMonitorStateException e) {
System.out.println("非锁持有者不能释放锁: " + e.getMessage());
}
}
}
3.7 newCondition() - 创建条件变量
创建与锁关联的Condition对象,用于实现等待/通知机制。一把锁可以创建多个Condition,这是相比synchronized的重要优势。
java
/**
* newCondition()使用示例
*/
public class ConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition(); // 非空条件
private final Condition notFull = lock.newCondition(); // 非满条件
private final Object[] items = new Object[10];
private int putIndex, takeIndex, count;
/**
* 生产者:添加元素
*/
public void put(Object item) throws InterruptedException {
lock.lock();
try {
// 队列满时,等待notFull条件
while (count == items.length) {
notFull.await();
}
items[putIndex] = item;
if (++putIndex == items.length) putIndex = 0;
count++;
// 通知消费者:队列非空了
notEmpty.signal();
} finally {
lock.unlock();
}
}
/**
* 消费者:取出元素
*/
public Object take() throws InterruptedException {
lock.lock();
try {
// 队列空时,等待notEmpty条件
while (count == 0) {
notEmpty.await();
}
Object item = items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
// 通知生产者:队列非满了
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
3.8 其他辅助方法
java
/**
* ReentrantLock的辅助方法
*/
public class AuxiliaryMethodsExample {
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
public void demonstrateAuxiliaryMethods() {
lock.lock();
try {
// 查询锁是否被任何线程持有
boolean locked = lock.isLocked();
System.out.println("锁是否被持有: " + locked); // true
// 查询当前线程的持有计数
int holdCount = lock.getHoldCount();
System.out.println("当前线程持有次数: " + holdCount); // 1
// 查询当前线程是否持有锁
boolean heldByMe = lock.isHeldByCurrentThread();
System.out.println("当前线程是否持有锁: " + heldByMe); // true
// 查询是否为公平锁
boolean fair = lock.isFair();
System.out.println("是否为公平锁: " + fair); // true
// 查询是否有线程在等待获取锁
boolean hasWaiters = lock.hasQueuedThreads();
System.out.println("是否有等待线程: " + hasWaiters);
// 查询等待队列长度
int queueLength = lock.getQueueLength();
System.out.println("等待队列长度: " + queueLength);
} finally {
lock.unlock();
}
}
}
4. 典型使用场景
4.1 场景一:线程安全的计数器
这是最基础的使用场景,确保多线程环境下计数操作的原子性。
java
/**
* 线程安全的计数器
*/
public class ThreadSafeCounter {
private final ReentrantLock lock = new ReentrantLock();
private long count = 0;
/**
* 原子递增
*/
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
/**
* 原子递减
*/
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
/**
* 获取当前值(需要加锁保证可见性)
*/
public long getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
/**
* 如果当前值等于预期值,则更新为新值
*/
public boolean compareAndSet(long expected, long newValue) {
lock.lock();
try {
if (count == expected) {
count = newValue;
return true;
}
return false;
} finally {
lock.unlock();
}
}
}
4.2 场景二:可中断的资源获取
在需要能够取消长时间等待的场景中使用lockInterruptibly()。
java
/**
* 可中断的资源池
*/
public class InterruptibleResourcePool {
private final ReentrantLock lock = new ReentrantLock();
private final Condition available = lock.newCondition();
private final List<Resource> resources = new ArrayList<>();
private final int maxSize;
public InterruptibleResourcePool(int maxSize) {
this.maxSize = maxSize;
for (int i = 0; i < maxSize; i++) {
resources.add(new Resource(i));
}
}
/**
* 可中断地获取资源
* 支持在等待过程中取消
*/
public Resource acquire() throws InterruptedException {
lock.lockInterruptibly(); // 可中断地获取锁
try {
// 等待可用资源
while (resources.isEmpty()) {
available.await(); // 可中断地等待
}
return resources.remove(0);
} finally {
lock.unlock();
}
}
/**
* 归还资源
*/
public void release(Resource resource) {
lock.lock();
try {
resources.add(resource);
available.signal(); // 通知等待线程
} finally {
lock.unlock();
}
}
/**
* 使用示例
*/
public static void main(String[] args) {
InterruptibleResourcePool pool = new InterruptibleResourcePool(2);
Thread worker = new Thread(() -> {
try {
Resource resource = pool.acquire();
try {
// 使用资源
resource.use();
} finally {
pool.release(resource);
}
} catch (InterruptedException e) {
System.out.println("资源获取被取消");
}
});
worker.start();
// 可以在需要时取消等待
// worker.interrupt();
}
}
class Resource {
private final int id;
Resource(int id) { this.id = id; }
void use() {
System.out.println("使用资源: " + id);
}
}
4.3 场景三:超时获取锁避免死锁
在可能发生死锁的场景中,使用tryLock(timeout)来避免永久阻塞。
java
/**
* 安全的银行转账(避免死锁)
*/
public class SafeBankTransfer {
/**
* 带超时的转账操作
* 通过超时机制避免死锁
*/
public boolean transfer(BankAccount from, BankAccount to,
double amount, long timeout, TimeUnit unit) {
long deadline = System.nanoTime() + unit.toNanos(timeout);
while (true) {
if (from.lock.tryLock()) {
try {
// 计算剩余超时时间
long remaining = deadline - System.nanoTime();
if (remaining <= 0) {
return false; // 超时
}
// 尝试获取第二把锁
if (to.lock.tryLock(remaining, TimeUnit.NANOSECONDS)) {
try {
// 检查余额
if (from.getBalance() < amount) {
throw new InsufficientFundsException();
}
// 执行转账
from.debit(amount);
to.credit(amount);
return true;
} finally {
to.lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
from.lock.unlock();
}
}
// 获取失败,短暂休眠后重试
if (System.nanoTime() >= deadline) {
return false; // 超时
}
try {
Thread.sleep(10); // 避免忙等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
}
class BankAccount {
final ReentrantLock lock = new ReentrantLock();
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public double getBalance() { return balance; }
public void debit(double amount) { balance -= amount; }
public void credit(double amount) { balance += amount; }
}
class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException() {
super("余额不足");
}
}
4.4 场景四:公平锁保证顺序
在需要严格按照请求顺序处理的场景中使用公平锁。
java
/**
* 公平的打印队列
* 确保打印任务按提交顺序执行
*/
public class FairPrintQueue {
// 使用公平锁,保证FIFO顺序
private final ReentrantLock lock = new ReentrantLock(true);
/**
* 打印文档
*/
public void printJob(String document) {
lock.lock();
try {
System.out.printf("[%s] 开始打印: %s%n",
Thread.currentThread().getName(), document);
// 模拟打印耗时
simulatePrinting();
System.out.printf("[%s] 完成打印: %s%n",
Thread.currentThread().getName(), document);
} finally {
lock.unlock();
}
}
private void simulatePrinting() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
FairPrintQueue queue = new FairPrintQueue();
// 创建10个打印任务
for (int i = 0; i < 10; i++) {
final int docNum = i;
new Thread(() -> {
queue.printJob("文档-" + docNum);
}, "Thread-" + i).start();
}
}
}
5. ReentrantLock vs synchronized
5.1 功能对比
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 实现方式 | JDK实现(纯Java) | JVM内置(native) |
| 锁获取方式 | 显式lock()/unlock() | 隐式,进入/退出代码块 |
| 可中断等待 | ✅ lockInterruptibly() | ❌ |
| 超时获取 | ✅ tryLock(timeout) | ❌ |
| 非阻塞获取 | ✅ tryLock() | ❌ |
| 公平锁 | ✅ 构造参数可选 | ❌ 非公平 |
| 条件变量 | ✅ 多个Condition | ❌ 单一wait/notify |
| 锁状态查询 | ✅ isLocked()等 | ❌ |
| 自动释放 | ❌ 必须手动unlock | ✅ 自动释放 |
| 锁绑定对象 | ReentrantLock实例 | 任意对象 |
5.2 性能对比

- JDK 1.5: ReentrantLock性能远优于synchronized
- JDK 1.6+: synchronized经过优化(偏向锁、轻量级锁),性能差距缩小
- 低竞争场景: synchronized略优(少了lock对象的开销)
- 高竞争场景: ReentrantLock更可控(可选公平锁、可中断)
5.3 代码对比
java
/**
* synchronized vs ReentrantLock 代码对比
*/
public class LockComparison {
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
// ========== 基本用法对比 ==========
public void synchronizedMethod() {
synchronized (syncLock) {
// 临界区
}
}
public void reentrantLockMethod() {
reentrantLock.lock();
try {
// 临界区
} finally {
reentrantLock.unlock();
}
}
// ========== 等待/通知对比 ==========
public void synchronizedWait() throws InterruptedException {
synchronized (syncLock) {
syncLock.wait(); // 等待
syncLock.notify(); // 通知单个
syncLock.notifyAll();// 通知所有
}
}
private final Condition condition = reentrantLock.newCondition();
public void reentrantLockWait() throws InterruptedException {
reentrantLock.lock();
try {
condition.await(); // 等待
condition.signal(); // 通知单个
condition.signalAll(); // 通知所有
} finally {
reentrantLock.unlock();
}
}
}
5.4 选择建议

选择synchronized的情况:
- 简单同步需求
- 不需要高级特性
- 追求代码简洁
- 团队对ReentrantLock不熟悉
选择ReentrantLock的情况:
- 需要可中断等待
- 需要超时获取
- 需要公平锁
- 需要多个条件变量
- 需要查询锁状态
- 需要更细粒度的锁控制
5.5 异常处理的关键区别
面试中常被忽略但非常重要的区别:
java
/**
* 异常处理对比 - 这是synchronized的重要优势
*/
public class ExceptionHandlingComparison {
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
/**
* synchronized:异常时自动释放锁
* 无论是正常退出还是异常退出,锁都会被自动释放
*/
public void synchronizedWithException() {
synchronized (syncLock) {
// 即使这里抛出异常,锁也会自动释放
throw new RuntimeException("出错了");
}
// 锁已自动释放,其他线程可以获取
}
/**
* ReentrantLock:必须在finally中释放锁
* 如果忘记finally或unlock,锁将永远不会释放!
*/
public void reentrantLockWithException() {
reentrantLock.lock();
try {
// 如果这里抛出异常...
throw new RuntimeException("出错了");
} finally {
// 必须在finally中释放!
reentrantLock.unlock();
}
}
/**
* 【危险】错误示范:忘记finally
*/
public void dangerousCode() {
reentrantLock.lock();
// 如果下面代码抛出异常,锁将永远不会释放!
doSomethingDangerous();
reentrantLock.unlock(); // 这行可能永远不会执行
}
private void doSomethingDangerous() {
throw new RuntimeException();
}
}
核心区别:
synchronized:JVM保证锁的释放,开发者无需关心ReentrantLock:开发者全权负责,必须配合try-finally使用
5.6 为什么有时synchronized更好
虽然ReentrantLock功能更强大,但以下场景synchronized是更好的选择:
java
/**
* synchronized更适合的场景
*/
public class WhenToUseSynchronized {
/**
* 场景1:简单的同步方法
* synchronized更简洁,不需要try-finally
*/
public synchronized void simpleMethod() {
// 一行搞定,简洁明了
}
/**
* 场景2:同步代码块很短
* JVM对synchronized有偏向锁、轻量级锁优化
*/
public void shortCriticalSection() {
synchronized (this) {
counter++; // 就这一行,用synchronized足够
}
}
private int counter;
/**
* 场景3:不需要ReentrantLock的高级特性
* 如果不需要中断、超时、公平锁、多条件变量
* 用synchronized更安全(不会忘记释放锁)
*/
public synchronized void noAdvancedFeatures() {
// 普通业务逻辑
}
/**
* 场景4:锁的粒度就是整个方法
* synchronized方法天然适合
*/
public synchronized void entireMethodNeedLock() {
step1();
step2();
step3();
}
private void step1() {}
private void step2() {}
private void step3() {}
}
总结:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 简单同步 | synchronized | 语法简洁,不易出错 |
| 需要中断/超时 | ReentrantLock | synchronized不支持 |
| 需要公平锁 | ReentrantLock | synchronized只有非公平 |
| 多个等待条件 | ReentrantLock | synchronized只有一个wait set |
| 性能敏感+低竞争 | synchronized | JVM优化更好 |
| 遗留代码维护 | synchronized | 保持一致性 |
5.7 锁的优缺点总结
在选择同步机制之前,先理解锁本身的优缺点:
锁的优点:
- 简单直观:比无锁编程容易理解和实现
- 功能完整:支持复杂的同步逻辑
- 可组合性:多个操作可以原子化执行
java
// 锁可以保护复杂的复合操作
lock.lock();
try {
if (map.containsKey(key)) {
map.put(key, map.get(key) + 1);
} else {
map.put(key, 1);
}
} finally {
lock.unlock();
}
锁的缺点:
- 性能开销:线程阻塞/唤醒涉及系统调用和上下文切换
- 死锁风险:多把锁可能造成死锁
- 优先级反转:低优先级线程持有锁时,高优先级线程被迫等待
- 不可组合:两个线程安全的操作组合后可能不安全
java
// 不可组合的例子:两个线程安全操作组合后不安全
// 假设list是CopyOnWriteArrayList(线程安全)
if (!list.contains(item)) { // 操作1:线程安全
list.add(item); // 操作2:线程安全
}
// 但整个if-then-add不是原子的!需要额外的锁
何时考虑其他方案:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单计数 | AtomicInteger | 无锁,性能更好 |
| 简单标志位 | volatile | 足够保证可见性 |
| 读多写少 | ReadWriteLock/StampedLock | 读操作并发 |
| 累加统计 | LongAdder | 高竞争下性能更好 |
| 单次初始化 | DCL或Holder模式 | 避免每次加锁 |
第二章:AQS 核心原理剖析
1. AQS设计思想
1.1 什么是AQS
AQS(AbstractQueuedSynchronizer)是Doug Lea设计的一个用于构建锁和同步器的框架。JUC(java.util.concurrent)包中的大部分同步器都是基于AQS实现的:
AQS的核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效工作线程,并将共享资源设置为锁定状态;如果被请求的共享资源被占用,就需要一套线程阻塞等待以及被唤醒时锁分配的机制,AQS使用CLH队列锁的变体来实现这一机制。
1.2 模板方法模式
AQS采用模板方法设计模式:将通用的流程封装在父类中,将可变的部分留给子类实现。
java
// AQS定义的模板方法(final,不可重写)
public final void acquire(int arg) {
if (!tryAcquire(arg) && // ① 子类实现:尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // ② AQS实现:入队等待
selfInterrupt();
}
public final boolean release(int arg) {
if (tryRelease(arg)) { // ① 子类实现:尝试释放锁
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // ② AQS实现:唤醒后继
return true;
}
return false;
}
子类需要实现的方法:
| 方法 | 说明 | 使用场景 |
|---|---|---|
tryAcquire(int) |
尝试独占获取 | ReentrantLock |
tryRelease(int) |
尝试独占释放 | ReentrantLock |
tryAcquireShared(int) |
尝试共享获取 | Semaphore、CountDownLatch |
tryReleaseShared(int) |
尝试共享释放 | Semaphore、CountDownLatch |
isHeldExclusively() |
是否独占持有 | Condition使用 |
1.3 两种资源共享模式
AQS支持两种资源共享模式:
独占模式(Exclusive):
- 同一时刻只有一个线程能获取资源
- 例如:ReentrantLock
共享模式(Shared):
- 多个线程可以同时获取资源
- 例如:Semaphore(信号量)、CountDownLatch、ReadWriteLock的读锁
2. 核心字段解析
2.1 state字段
state是AQS中最核心的字段,表示同步状态。它的含义由具体的同步器定义:
java
/**
* The synchronization state.
*/
private volatile int state;
// 获取状态
protected final int getState() {
return state;
}
// 设置状态
protected final void setState(int newState) {
state = newState;
}
// CAS设置状态
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
不同同步器中state的含义:
| 同步器 | state含义 |
|---|---|
| ReentrantLock | 0=未锁定,>0=锁定(值表示重入次数) |
| ReentrantReadWriteLock | 高16位=读锁数量,低16位=写锁重入次数 |
| Semaphore | 可用许可数量 |
| CountDownLatch | 剩余计数 |
java
/**
* ReentrantLock中state的使用
*/
// 获取锁时
if (compareAndSetState(0, 1)) { // 0 -> 1,获取成功
setExclusiveOwnerThread(current);
}
// 重入时
int nextc = c + acquires; // state递增
setState(nextc);
// 释放锁时
int c = getState() - releases; // state递减
if (c == 0) {
setExclusiveOwnerThread(null); // 完全释放
}
setState(c);
2.2 head和tail字段
AQS使用一个FIFO双向队列来管理等待线程。head和tail分别指向队列的头部和尾部:
java
/**
* Head of the wait queue, lazily initialized.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized.
*/
private transient volatile Node tail;

关键点:
- 懒初始化:只有在发生竞争时才会初始化队列
- head是哨兵节点:不存储实际的等待线程,简化边界处理
- 双向链表:便于节点的取消和超时处理
2.3 exclusiveOwnerThread字段
该字段继承自AbstractOwnableSynchronizer,记录当前持有独占锁的线程:
java
// AbstractOwnableSynchronizer.java
public abstract class AbstractOwnableSynchronizer {
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
这个字段用于:
- 可重入判断:检查当前线程是否已经持有锁
- 锁状态查询:获取锁的持有者
3. CLH队列变体
3.1 经典CLH队列
CLH(Craig, Landin, and Hagersten)队列是一种基于链表的自旋锁,每个线程在前驱节点上自旋等待:
经典CLH特点:
- 每个节点只有一个
locked标志 - 后继节点在前驱的
locked上自旋 - 释放锁时,只需设置自己的
locked=false
3.2 AQS的CLH变体
AQS对CLH队列进行了改造,主要区别:
主要改进:
| 特性 | 经典CLH | AQS CLH变体 |
|---|---|---|
| 链表方向 | 单向(隐式) | 双向(显式prev/next) |
| 等待方式 | 自旋 | park/unpark阻塞 |
| 节点状态 | 简单locked | 多种waitStatus |
| 取消支持 | 不支持 | 支持(CANCELLED状态) |
| 超时支持 | 不支持 | 支持 |
3.3 为什么使用双向链表
单向链表的问题:
- 取消节点时,无法快速找到前驱来修改next指针
- 超时/中断时需要从头遍历
双向链表的优势:
- O(1)时间找到前驱节点
- 便于实现取消和超时逻辑
- 唤醒后继时更高效
java
// 取消节点时需要找到有效前驱
private void cancelAcquire(Node node) {
// ...
Node pred = node.prev;
while (pred.waitStatus > 0) // 跳过已取消的前驱
node.prev = pred = pred.prev;
// ...
}
3.4 自旋锁与AQS的自旋机制
什么是自旋锁?
自旋锁是一种忙等待锁,线程在获取锁失败时不会立即阻塞,而是在循环中不断尝试获取锁:
java
/**
* 简单自旋锁示例
*/
public class SimpleSpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
// 自旋:不断尝试,直到成功
while (!locked.compareAndSet(false, true)) {
// 自旋等待,消耗CPU
}
}
public void unlock() {
locked.set(false);
}
}
自旋的优缺点:
| 方面 | 优点 | 缺点 |
|---|---|---|
| CPU使用 | 无上下文切换 | 持续消耗CPU |
| 延迟 | 锁释放后立即获取 | 长时间等待浪费CPU |
| 适用场景 | 锁持有时间短 | 锁持有时间长时效率低 |
AQS中的自旋机制:
AQS并非纯自旋锁 ,而是采用自旋+阻塞的混合策略:
java
/**
* acquireQueued中的自旋逻辑
* 关键:不是一直自旋,而是有条件地自旋
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { // 这是一个"自旋"循环
final Node p = node.predecessor();
// 【自旋尝试1】只有前驱是head时才尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
// 【关键】不满足条件时,不是继续自旋,而是park阻塞!
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
AQS自旋策略的精妙之处:
- 有限自旋:只有前驱是head(即马上轮到自己)时才自旋尝试
- 快速阻塞:其他情况直接park阻塞,避免CPU空转
- 唤醒后自旋:被unpark唤醒后,再次进入循环尝试
java
// 为什么要在park前再自旋一次?
// 因为在设置SIGNAL和park之间,锁可能已经释放了
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true; // 可以安全阻塞
// ... 设置SIGNAL
return false; // 返回false,外层循环会再尝试一次acquire!
}
3.5 锁的内存语义
从 JMM 的角度看,锁既提供互斥,也提供内存可见性保障。具体来说:对同一把锁的释放与获取之间存在"happens-before" 关系。一个线程在释放锁时,会把临界区中的所有写操作刷回主内存(类似于对 volatile 变量的写);另一个线程之后获取同一把锁时,必须从主内存中重新读取这些变量,从而看到之前线程的修改,这类似于对 volatile 变量的读。
在 ReentrantLock 内部,这种语义是通过 AQS 的 state 字段(volatile)和 CAS 操作实现的:获取锁使用 CAS 修改 state,相当于一次"volatile 读 + volatile 写";释放锁使用对 state 的普通写,但 state 本身是 volatile 变量,这会把之前的修改刷新到主内存。这样,临界区内的所有普通写在释放锁后,对后续获取同一把锁的线程都是可见的。
为什么理解内存语义很重要?
锁不仅提供互斥,还提供内存可见性保证。这是很多开发者忽略的关键点。
锁的内存语义:
- 获取锁:相当于读取volatile变量,会清空本地缓存,从主内存重新读取
- 释放锁:相当于写入volatile变量,会将本地缓存刷新到主内存
java
/**
* 锁的内存语义示例
*/
public class LockMemorySemantics {
private int x = 0;
private int y = 0;
private final ReentrantLock lock = new ReentrantLock();
public void writer() {
lock.lock(); // 获取锁:后续读操作不会重排序到这之前
try {
x = 1; // 这些写入在释放锁时会刷新到主内存
y = 2;
} finally {
lock.unlock(); // 释放锁:之前的写操作对其他线程可见
}
}
public void reader() {
lock.lock(); // 获取锁:会看到释放锁之前的所有写入
try {
// 这里一定能看到 x=1, y=2
System.out.println(x + ", " + y);
} finally {
lock.unlock();
}
}
}
AQS如何实现内存语义?
通过volatile变量state和CAS操作:
java
// 获取锁时的内存语义
protected final boolean compareAndSetState(int expect, int update) {
// CAS操作具有volatile读+写的内存语义
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// 释放锁时的内存语义
protected final void setState(int newState) {
state = newState; // volatile写,刷新缓存到主内存
}
volatile vs 锁:
| 特性 | volatile | 锁 |
|---|---|---|
| 可见性 | ✅ 保证 | ✅ 保证 |
| 原子性 | ❌ 只保证单个读/写 | ✅ 保证临界区原子性 |
| 有序性 | ✅ 禁止重排序 | ✅ 禁止重排序 |
| 复合操作 | ❌ 不支持 | ✅ 支持 |
| 性能 | 更好(无阻塞) | 有阻塞开销 |
java
// volatile适合的场景:状态标志
private volatile boolean shutdown = false;
public void shutdown() {
shutdown = true; // 单个写操作,volatile足够
}
public void doWork() {
while (!shutdown) { // 单个读操作
// ...
}
}
// volatile不适合的场景:复合操作
private volatile int count = 0;
public void increment() {
count++; // 【错误】这是读-改-写,不是原子操作!
}
// 正确做法:使用锁或AtomicInteger
private final ReentrantLock lock = new ReentrantLock();
public void safeIncrement() {
lock.lock();
try {
count++; // 现在是线程安全的
} finally {
lock.unlock();
}
}
4. Node节点详解
4.1 Node结构
java
static final class Node {
/** 共享模式标记 */
static final Node SHARED = new Node();
/** 独占模式标记 */
static final Node EXCLUSIVE = null;
/** waitStatus值:表示线程已取消 */
static final int CANCELLED = 1;
/** waitStatus值:表示后继线程需要唤醒 */
static final int SIGNAL = -1;
/** waitStatus值:表示线程在条件队列中等待 */
static final int CONDITION = -2;
/** waitStatus值:表示共享模式下无条件传播 */
static final int PROPAGATE = -3;
/** 等待状态 */
volatile int waitStatus;
/** 前驱节点 */
volatile Node prev;
/** 后继节点 */
volatile Node next;
/** 节点中的线程 */
volatile Thread thread;
/** 链接下一个等待条件的节点,或特殊值SHARED */
Node nextWaiter;
}
4.2 waitStatus状态详解
waitStatus是Node中最复杂的字段,用于表示节点的等待状态:

| 状态值 | 名称 | 含义 |
|---|---|---|
| 0 | 初始状态 | 新建节点的默认状态 |
| -1 | SIGNAL | 当前节点释放锁后需要唤醒后继节点 |
| 1 | CANCELLED | 线程已取消(超时或中断) |
| -2 | CONDITION | 节点在条件队列中等待 |
| -3 | PROPAGATE | 共享模式下,释放操作需要传播 |
SIGNAL状态的重要性:
java
// 节点入队后,需要将前驱的waitStatus设为SIGNAL
// 这样前驱释放锁时才会唤醒当前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱已经是SIGNAL,可以安全阻塞
return true;
if (ws > 0) {
// 前驱已取消,跳过
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将前驱状态设为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
4.3 节点模式
java
// 判断是否为共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 创建节点
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
5. 独占模式获取锁
5.1 acquire流程概览
java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
整体流程如下:

5.2 tryAcquire - 子类实现
以ReentrantLock的非公平锁为例:
java
// NonfairSync.tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// Sync.nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 锁空闲,CAS尝试获取
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 当前线程已持有锁,重入
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
5.3 addWaiter - 创建节点入队
java
private Node addWaiter(Node mode) {
// 创建新节点
Node node = new Node(Thread.currentThread(), mode);
// 快速路径:尝试直接挂到队尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速路径失败,进入完整入队流程
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
// 队列为空,初始化(创建哨兵节点)
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 将节点挂到队尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
入队过程图解:
5.4 acquireQueued - 排队获取锁
这是最核心的方法,线程在队列中自旋或阻塞,直到获取到锁:
java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取前驱节点
final Node p = node.predecessor();
// 如果前驱是head,说明当前节点是第一个等待者,尝试获取锁
if (p == head && tryAcquire(arg)) {
// 获取成功,将当前节点设为head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 判断是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
acquireQueued流程图:

5.5 shouldParkAfterFailedAcquire - 是否阻塞
java
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 前驱状态正常,当前线程可以安全阻塞
return true;
if (ws > 0) {
// 前驱已取消,跳过所有已取消的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 将前驱状态设为SIGNAL
// 下次循环会返回true
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
为什么不直接阻塞,而是先设置前驱为SIGNAL?
这是为了确保当前节点阻塞后能被正确唤醒。如果直接阻塞,而前驱的waitStatus不是SIGNAL,那么前驱释放锁时不会唤醒当前节点,导致永久阻塞。
5.6 parkAndCheckInterrupt - 阻塞线程
java
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞当前线程
return Thread.interrupted(); // 返回并清除中断状态
}
LockSupport.park()会阻塞当前线程,直到以下情况之一发生:
- 其他线程调用
unpark()唤醒 - 线程被中断
- 虚假唤醒(spurious wakeup)
6. 独占模式释放锁
6.1 release流程
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

6.2 tryRelease - 子类实现
java
// ReentrantLock.Sync.tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 只有锁持有者才能释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// state减到0,完全释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
6.3 unparkSuccessor - 唤醒后继
java
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 找到需要唤醒的后继节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// next为空或已取消,从tail向前找
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
为什么要从tail向前找?
因为在addWaiter中,设置prev指针和next指针不是原子操作:
java
// addWaiter中的代码
node.prev = pred; // ① 先设置prev
if (compareAndSetTail(pred, node)) {
pred.next = node; // ② 后设置next
return node;
}
在①和②之间,如果发生并发操作,从head向后遍历可能找不到新入队的节点,但从tail向前遍历一定能找到。
6.4 完整的加锁-释放锁流程
第三章:ReentrantLock 源码深度剖析
1. 类结构设计
1.1 整体结构
ReentrantLock采用组合模式 ,通过内部类Sync及其子类实现锁的核心逻辑:
java
public class ReentrantLock implements Lock, java.io.Serializable {
/** 同步器,提供所有实现机制 */
private final Sync sync;
/** 同步器基类 */
abstract static class Sync extends AbstractQueuedSynchronizer { ... }
/** 非公平锁实现 */
static final class NonfairSync extends Sync { ... }
/** 公平锁实现 */
static final class FairSync extends Sync { ... }
// 构造方法
public ReentrantLock() {
sync = new NonfairSync(); // 默认非公平锁
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
1.2 类图

1.3 设计亮点
- 策略模式 :通过
Sync子类切换公平/非公平策略 - 模板方法 :继承AQS,只重写
tryAcquire和tryRelease - 组合优于继承:ReentrantLock组合Sync,而非直接继承AQS
- 内部类封装:Sync类对外不可见,隐藏实现细节
2. Sync抽象类
2.1 核心方法概览
java
abstract static class Sync extends AbstractQueuedSynchronizer {
/** 获取锁,由子类实现不同策略 */
abstract void lock();
/** 非公平tryAcquire,公平锁和非公平锁的tryLock()都用它 */
final boolean nonfairTryAcquire(int acquires) { ... }
/** 释放锁,公平和非公平逻辑相同 */
protected final boolean tryRelease(int releases) { ... }
/** 判断当前线程是否持有锁 */
protected final boolean isHeldExclusively() { ... }
/** 创建条件变量 */
final ConditionObject newCondition() { ... }
// 辅助方法
final Thread getOwner() { ... }
final int getHoldCount() { ... }
final boolean isLocked() { ... }
}
2.2 nonfairTryAcquire - 非公平获取锁
这是ReentrantLock的核心方法,实现了非公平的锁获取逻辑。公平锁和非公平锁的tryLock()方法都会调用它:
java
/**
* 非公平地尝试获取锁
* @param acquires 获取数量(通常为1)
* @return 是否获取成功
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// ① 锁空闲
if (c == 0) {
// 直接CAS尝试获取,不检查队列(非公平)
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// ② 当前线程已持有锁(重入)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
// 溢出检查(最大重入次数约21亿)
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// ③ 锁被其他线程持有
return false;
}
执行流程:

2.3 tryRelease - 释放锁
公平锁和非公平锁的释放逻辑完全相同:
java
/**
* 尝试释放锁
* @param releases 释放数量(通常为1)
* @return 是否完全释放(state变为0)
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// ① 只有锁持有者才能释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// ② state减到0,完全释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// ③ 更新state(无需CAS,因为是独占的)
setState(c);
return free;
}
关键点:
- 只有
state==0时才真正释放锁(返回true) - 重入场景下,每次unlock只将state减1
- 不需要CAS,因为只有锁持有者才能调用
2.4 isHeldExclusively - 是否独占持有
java
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
这个方法被Condition使用,用于在await()和signal()时检查当前线程是否持有锁。
2.5 辅助方法
java
// 获取锁的持有者
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 获取当前线程的重入次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 判断锁是否被任何线程持有
final boolean isLocked() {
return getState() != 0;
}
// 创建条件变量
final ConditionObject newCondition() {
return new ConditionObject();
}
3. 非公平锁NonfairSync
3.1 完整源码
java
/**
* 非公平锁的同步器
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 获取锁
* 非公平锁的特点:先尝试插队,失败再排队
*/
final void lock() {
// ① 直接尝试CAS获取锁(插队)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// ② 插队失败,走标准获取流程
acquire(1);
}
/**
* 尝试获取锁(非公平版本)
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3.2 非公平锁的"插队"机制
非公平锁有两次插队机会:
第一次插队 :在lock()方法中直接CAS
java
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
第二次插队 :在acquire()中调用tryAcquire()
java
// AQS.acquire
if (!tryAcquire(arg) && ...)
这就是为什么非公平锁可能导致等待线程"饥饿"------新来的线程可能抢在等待队列前面获取锁。
3.3 非公平锁的饥饿问题
3.3.1 什么是线程饥饿
java
/**
* 非公平锁可能导致线程饥饿
*/
public class ThreadStarvation {
// 非公平锁(默认)
private final ReentrantLock unfairLock = new ReentrantLock(false);
/**
* 场景:线程A持有锁很长时间
* 线程B、C、D...都在等待
* 线程A释放后,如果新来的线程E抢到了锁
* B、C、D继续等待...
*
* 极端情况下,B可能一直获取不到锁
*/
public void potentialStarvation() {
unfairLock.lock();
try {
// 长时间操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
unfairLock.unlock();
}
}
}
3.3.2 如何解决饥饿问题
java
/**
* 解决饥饿问题的方案
*/
public class StarvationSolution {
// 方案1:使用公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);
// 方案2:减少锁持有时间
private final ReentrantLock lock = new ReentrantLock();
public void reduceHoldTime() {
lock.lock();
try {
// 只做必要操作
quickOperation();
} finally {
lock.unlock();
}
// 耗时操作放在锁外
slowOperation();
}
// 方案3:使用tryLock超时
public void withTimeout() {
try {
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// 业务逻辑
} finally {
lock.unlock();
}
} else {
// 超时处理,而不是无限等待
handleTimeout();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void quickOperation() {}
private void slowOperation() {}
private void handleTimeout() {}
}
3.3.3 为什么还要使用非公平锁
尽管可能造成饥饿,非公平锁仍是默认选择:
| 方面 | 公平锁 | 非公平锁 |
|---|---|---|
| 线程饥饿 | 不会 | 可能 |
| 吞吐量 | 较低 | 较高 |
| 上下文切换 | 多 | 少 |
| 适用场景 | 顺序重要 | 性能重要 |
大多数场景下,饥饿问题发生的概率很低,非公平锁的性能优势更重要。
4. 公平锁FairSync
4.1 完整源码
java
/**
* 公平锁的同步器
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* 获取锁
* 公平锁的特点:不插队,直接走标准流程
*/
final void lock() {
acquire(1); // 没有插队,直接acquire
}
/**
* 尝试获取锁(公平版本)
* 与非公平版本的唯一区别:检查是否有前驱等待者
*/
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;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入逻辑与非公平锁相同
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
4.2 hasQueuedPredecessors - 检查前驱等待者
这是公平锁的核心方法,判断当前线程是否需要排队:
java
/**
* 查询是否有线程比当前线程等待更久
* @return true表示有前驱等待者,当前线程需要排队
*/
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
逻辑解析:

返回true的情况(需要排队):
- 队列不为空,且有其他线程在等待
- 队列正在初始化(head.next为null)
返回false的情况(可以尝试获取):
- 队列为空
- 当前线程就是第一个等待者
5. 公平锁vs非公平锁对比
5.1 代码差异
java
// ============ 非公平锁 ============
static final class NonfairSync extends Sync {
final void lock() {
// 差异1:直接尝试CAS插队
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 差异2:不检查队列,直接尝试获取
return nonfairTryAcquire(acquires);
}
}
// ============ 公平锁 ============
static final class FairSync extends Sync {
final void lock() {
// 差异1:没有插队,直接acquire
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// ...
if (c == 0) {
// 差异2:先检查是否有等待者
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// ...
}
}
// ...
}
}
5.2 行为对比
| 方面 | 非公平锁 | 公平锁 |
|---|---|---|
| lock()首次尝试 | CAS插队 | 直接acquire |
| tryAcquire检查 | 不检查队列 | 检查hasQueuedPredecessors |
| 插队机会 | 2次 | 0次 |
| 饥饿可能 | 有 | 无 |
| 吞吐量 | 高 | 低 |
| 线程切换 | 少 | 多 |
5.3 性能差异分析
非公平锁更快的原因:
非公平锁减少的开销:
- 减少线程切换次数
- 避免"刚唤醒就要睡眠"的情况
- 提高CPU缓存命中率
公平锁更慢的原因:
- 每次获取锁都要检查队列
- 必须严格按顺序唤醒
- 更多的线程切换
5.4 选择建议

使用非公平锁的场景(默认):
- 大多数业务场景
- 追求高吞吐量
- 锁持有时间短
使用公平锁的场景:
- 严格要求按顺序处理
- 避免线程饥饿
- 公平性比性能更重要
5.5 tryLock在公平/非公平锁中的行为
5.5.1 关键区别
java
/**
* tryLock()在公平锁和非公平锁中的行为差异
*
* 【重要】tryLock()不遵循公平性!
*/
public class TryLockBehavior {
// 公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);
public void demonstrateTryLock() {
// lock()方法遵循公平性
// 公平锁:检查队列,有等待者则不插队
// 非公平锁:直接尝试获取
// tryLock()方法【不遵循公平性】!
// 即使是公平锁,tryLock()也会直接尝试获取,不检查队列!
if (fairLock.tryLock()) { // 插队获取,不管队列中是否有等待者
try {
// ...
} finally {
fairLock.unlock();
}
}
// 如果需要公平的tryLock,使用带超时的版本:
// tryLock(0, TimeUnit.SECONDS) 会遵循公平性!
}
}
5.5.2 源码分析
java
// ReentrantLock.tryLock() 源码
public boolean tryLock() {
return sync.nonfairTryAcquire(1); // 【注意】调用的是nonfairTryAcquire!
}
// ReentrantLock.tryLock(timeout, unit) 源码
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout)); // 这个会检查公平性
}
为什么tryLock()不遵循公平性?
- 设计意图:tryLock()是"尝试获取",如果要排队那就不是"尝试"了
- 性能考虑:检查队列需要额外开销
- 使用场景:通常用于"能获取就获取,不能就算了"的场景
6. 可重入机制详解
6.1 什么是可重入
可重入(Reentrant)是指同一个线程可以多次获取同一把锁,而不会造成死锁。
java
/**
* 可重入演示
*/
public class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void methodA() {
lock.lock(); // 第一次获取,state = 1
try {
System.out.println("methodA");
methodB(); // 调用methodB,再次获取锁
} finally {
lock.unlock(); // state = 0
}
}
public void methodB() {
lock.lock(); // 第二次获取,state = 2(重入!)
try {
System.out.println("methodB");
} finally {
lock.unlock(); // state = 1
}
}
}
6.2 重入实现原理
获取锁时的重入判断:
java
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 锁空闲,首次获取
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入判断:当前线程是否是锁持有者
else if (current == getExclusiveOwnerThread()) {
// 是,state累加
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); // 无需CAS,因为是独占的
return true;
}
return false;
}
释放锁时的重入处理:
java
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // state减1
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 只有state减到0才真正释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free; // 返回是否完全释放
}
6.3 重入过程图解
锁完全释放
6.4 为什么需要可重入
如果锁不支持重入,会发生死锁:
java
/**
* 假设锁不可重入
*/
public class NonReentrantProblem {
private final Object lock = new Object();
public synchronized void methodA() {
System.out.println("methodA");
methodB(); // 死锁!当前线程已持有锁,无法再次获取
}
public synchronized void methodB() {
System.out.println("methodB");
}
}
可重入的意义:
- 支持递归调用
- 支持同步方法相互调用
- 简化编程模型,避免意外死锁
6.5 最大重入次数
java
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
由于state是int类型,最大重入次数约为Integer.MAX_VALUE(约21亿)。实际使用中基本不可能达到这个限制。
7. 辅助方法源码分析
7.1 getHoldCount - 获取重入次数
java
/**
* 获取当前线程持有锁的次数
*/
public int getHoldCount() {
return sync.getHoldCount();
}
// Sync.getHoldCount
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
使用场景:调试和测试
java
public void debugMethod() {
lock.lock();
try {
System.out.println("重入次数: " + lock.getHoldCount()); // 1
lock.lock();
try {
System.out.println("重入次数: " + lock.getHoldCount()); // 2
} finally {
lock.unlock();
}
System.out.println("重入次数: " + lock.getHoldCount()); // 1
} finally {
lock.unlock();
}
}
7.2 isHeldByCurrentThread - 当前线程是否持有锁
java
/**
* 查询当前线程是否持有锁
*/
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
// Sync.isHeldExclusively
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
使用场景:断言检查
java
public void criticalSection() {
// 确保进入临界区时持有锁
assert lock.isHeldByCurrentThread();
// 临界区代码...
}
7.3 isLocked - 锁是否被任何线程持有
java
/**
* 查询锁是否被任何线程持有
*/
public boolean isLocked() {
return sync.isLocked();
}
// Sync.isLocked
final boolean isLocked() {
return getState() != 0;
}
注意:这个方法返回的是"快照",返回后状态可能已经改变。主要用于监控。
7.4 getOwner - 获取锁持有者
java
/**
* 获取锁的持有线程
* @return 持有线程,如果未锁定则返回null
*/
protected Thread getOwner() {
return sync.getOwner();
}
// Sync.getOwner
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
注意:这是protected方法,主要供子类使用。
7.5 hasQueuedThreads/getQueueLength - 队列相关方法
java
/**
* 查询是否有线程在等待获取锁
*/
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
/**
* 获取等待队列的长度估计值
*/
public final int getQueueLength() {
return sync.getQueueLength();
}
使用场景:监控和调优
java
public void monitorLock() {
System.out.println("是否有等待线程: " + lock.hasQueuedThreads());
System.out.println("等待队列长度: " + lock.getQueueLength());
}
7.6 isFair - 是否为公平锁
java
/**
* 判断是否为公平锁
*/
public final boolean isFair() {
return sync instanceof FairSync;
}
8. ReentrantLock vs ReentrantReadWriteLock
8.1 核心区别
java
/**
* ReentrantLock vs ReentrantReadWriteLock
*/
public class LockComparison {
// ReentrantLock:互斥锁,任何时刻只有一个线程能访问
private final ReentrantLock mutexLock = new ReentrantLock();
// ReentrantReadWriteLock:读写锁,读操作可并发
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private Object data;
// 使用互斥锁:读写都互斥
public Object readWithMutex() {
mutexLock.lock(); // 即使是读操作也要独占
try {
return data;
} finally {
mutexLock.unlock();
}
}
// 使用读写锁:读操作可并发
public Object readWithRwLock() {
readLock.lock(); // 多个读线程可以同时持有读锁
try {
return data;
} finally {
readLock.unlock();
}
}
public void writeWithRwLock(Object newData) {
writeLock.lock(); // 写锁是独占的
try {
data = newData;
} finally {
writeLock.unlock();
}
}
}
8.2 锁降级
ReentrantLock不支持锁降级 ,而ReentrantReadWriteLock支持:
java
/**
* 锁降级:写锁 -> 读锁
* 只有ReadWriteLock支持,ReentrantLock不支持
*/
public class LockDowngrade {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private volatile boolean cacheValid = false;
private Object cachedData;
/**
* 锁降级示例
*/
public Object processData() {
readLock.lock(); // 先获取读锁
try {
if (!cacheValid) {
// 缓存无效,需要写入
readLock.unlock(); // 必须先释放读锁
writeLock.lock(); // 再获取写锁
try {
// 双重检查
if (!cacheValid) {
cachedData = loadFromDB();
cacheValid = true;
}
// 【锁降级】在释放写锁前获取读锁
readLock.lock();
} finally {
writeLock.unlock(); // 释放写锁,仍持有读锁
}
}
// 此时持有读锁,可以安全读取
return cachedData;
} finally {
readLock.unlock();
}
}
private Object loadFromDB() { return new Object(); }
}
为什么需要锁降级?
- 在更新数据后,仍需要使用该数据
- 如果直接释放写锁,其他线程可能修改数据
- 锁降级保证了数据的一致性
为什么ReentrantLock不需要锁降级?
- ReentrantLock只有一种锁,没有读写之分
- 可重入特性已经满足了类似需求
9. 锁测试方法详解
9.1 ReentrantLock提供的测试方法
java
/**
* ReentrantLock的锁测试方法
*/
public class LockTestMethods {
private final ReentrantLock lock = new ReentrantLock();
public void demonstrateTestMethods() {
// 1. isLocked() - 锁是否被任何线程持有
boolean locked = lock.isLocked();
System.out.println("锁是否被持有: " + locked);
// 2. isHeldByCurrentThread() - 当前线程是否持有锁
boolean heldByMe = lock.isHeldByCurrentThread();
System.out.println("当前线程是否持有: " + heldByMe);
// 3. getHoldCount() - 当前线程的重入次数
int holdCount = lock.getHoldCount();
System.out.println("重入次数: " + holdCount);
// 4. hasQueuedThreads() - 是否有线程在等待获取锁
boolean hasWaiters = lock.hasQueuedThreads();
System.out.println("是否有等待线程: " + hasWaiters);
// 5. getQueueLength() - 等待获取锁的线程数(估计值)
int queueLength = lock.getQueueLength();
System.out.println("等待线程数: " + queueLength);
// 6. isFair() - 是否为公平锁
boolean fair = lock.isFair();
System.out.println("是否公平锁: " + fair);
// 7. getOwner() - 获取持有锁的线程(protected方法,需要子类访问)
// Thread owner = lock.getOwner(); // 需要继承ReentrantLock
}
/**
* 实际应用:安全地释放锁
*/
public void safeUnlock() {
// 只有持有锁的线程才能释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* 实际应用:检查死锁风险
*/
public void checkDeadlockRisk() {
if (lock.isLocked() && !lock.isHeldByCurrentThread()) {
System.out.println("警告:锁被其他线程持有,可能造成等待");
}
}
}
第四章:Condition 条件变量详解
1. Condition接口概述
1.1 什么是Condition
Condition是JDK 1.5引入的条件变量接口,用于实现线程间的等待/通知机制。它必须与Lock配合使用,提供了比Object.wait/notify更强大、更灵活的功能。
java
public interface Condition {
/** 等待,响应中断 */
void await() throws InterruptedException;
/** 等待,不响应中断 */
void awaitUninterruptibly();
/** 等待指定纳秒 */
long awaitNanos(long nanosTimeout) throws InterruptedException;
/** 等待指定时间 */
boolean await(long time, TimeUnit unit) throws InterruptedException;
/** 等待到指定时刻 */
boolean awaitUntil(Date deadline) throws InterruptedException;
/** 唤醒一个等待线程 */
void signal();
/** 唤醒所有等待线程 */
void signalAll();
}
1.2 为什么需要Condition
synchronized配合Object.wait/notify的局限性:
java
/**
* Object.wait/notify的局限性演示
*/
public class ObjectWaitLimitation {
private final Object lock = new Object();
private boolean hasData = false;
private boolean hasSpace = true;
/**
* 问题:生产者和消费者共用一个等待队列
* notify()无法精确唤醒特定类型的线程
*/
public void produce() throws InterruptedException {
synchronized (lock) {
while (!hasSpace) {
lock.wait(); // 等待空间
}
// 生产数据...
hasData = true;
hasSpace = false;
lock.notifyAll(); // 只能唤醒所有,包括其他生产者
}
}
public void consume() throws InterruptedException {
synchronized (lock) {
while (!hasData) {
lock.wait(); // 等待数据
}
// 消费数据...
hasData = false;
hasSpace = true;
lock.notifyAll(); // 只能唤醒所有,包括其他消费者
}
}
}
Condition的解决方案:
java
/**
* Condition支持多个等待队列
*/
public class ConditionSolution {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition(); // 数据可用条件
private final Condition notFull = lock.newCondition(); // 空间可用条件
public void produce() throws InterruptedException {
lock.lock();
try {
while (isFull()) {
notFull.await(); // 只在"非满"条件上等待
}
// 生产数据...
notEmpty.signal(); // 只唤醒"非空"条件上的消费者
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (isEmpty()) {
notEmpty.await(); // 只在"非空"条件上等待
}
// 消费数据...
notFull.signal(); // 只唤醒"非满"条件上的生产者
} finally {
lock.unlock();
}
}
private boolean isFull() { return false; /* 简化 */ }
private boolean isEmpty() { return true; /* 简化 */ }
}
2. Condition vs Object监视器方法
2.1 功能对比
| 功能 | Object方法 | Condition方法 |
|---|---|---|
| 等待 | wait() |
await() |
| 等待(不响应中断) | - | awaitUninterruptibly() |
| 超时等待 | wait(timeout) |
await(time, unit) |
| 等待到指定时刻 | - | awaitUntil(deadline) |
| 唤醒单个 | notify() |
signal() |
| 唤醒所有 | notifyAll() |
signalAll() |
| 等待队列数量 | 1个 | 多个 |
| 是否可中断 | 是 | 可选 |
2.2 使用方式对比
java
// ========== Object方式 ==========
synchronized (obj) {
while (条件不满足) {
obj.wait();
}
// 执行操作
obj.notify();
}
// ========== Condition方式 ==========
lock.lock();
try {
while (条件不满足) {
condition.await();
}
// 执行操作
condition.signal();
} finally {
lock.unlock();
}
2.3 多条件变量的优势

3. ConditionObject源码剖析
3.1 类结构
ConditionObject是AQS的内部类,实现了Condition接口:
java
public class ConditionObject implements Condition, java.io.Serializable {
/** 条件队列的首节点 */
private transient Node firstWaiter;
/** 条件队列的尾节点 */
private transient Node lastWaiter;
/** 中断模式:退出wait时重新中断 */
private static final int REINTERRUPT = 1;
/** 中断模式:退出wait时抛出InterruptedException */
private static final int THROW_IE = -1;
}
3.2 条件队列结构
条件队列是一个单向链表 ,使用Node的nextWaiter字段链接:

与同步队列的区别:
| 特性 | 同步队列(AQS) | 条件队列(Condition) |
|---|---|---|
| 链表类型 | 双向链表 | 单向链表 |
| 链接字段 | prev/next | nextWaiter |
| 节点状态 | 多种 | 只有CONDITION |
| 用途 | 等待获取锁 | 等待条件满足 |
3.3 Node在条件队列中的复用
java
static final class Node {
// 同步队列使用
volatile Node prev;
volatile Node next;
// 条件队列使用(复用)
Node nextWaiter;
// 等待状态
volatile int waitStatus;
// CONDITION状态,表示在条件队列中
static final int CONDITION = -2;
}
4. 条件队列与同步队列
在 ReentrantLock + Condition 的语境下,等待通知机制大致分为两层:Condition 条件队列 和 AQS 同步队列。
当线程在持有锁的前提下调用 condition.await(),它会先把自己从同步队列移到条件队列,释放掉当前持有的锁,然后通过 LockSupport.park() 阻塞自己。之后,当其它线程在持有同一把锁的情况下调用 condition.signal() 或 signalAll(),被唤醒的节点会从条件队列被转移回同步队列,此时它并没有立刻恢复执行,而是重新去参与 AQS 级别的抢锁,只有当再次获取到锁之后才真正返回 await() 之后的那一行去继续执行。
这样,Condition 保证了"等待条件时先释放锁,避免占着锁睡觉;条件满足时先唤醒,再按锁的排队规则依次继续"的语义,这比直接 Object.wait/notify 要清晰和强大得多,尤其在存在多个条件时。
4.1 两个队列的关系

4.2 节点流转过程
当调用await()和signal()时,节点会在两个队列之间流转:
5. await核心流程
5.1 await方法源码
java
public final void await() throws InterruptedException {
// ① 检查中断
if (Thread.interrupted())
throw new InterruptedException();
// ② 创建节点加入条件队列
Node node = addConditionWaiter();
// ③ 完全释放锁(包括重入),保存state
int savedState = fullyRelease(node);
int interruptMode = 0;
// ④ 循环检查是否在同步队列中
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // 阻塞
// 检查中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// ⑤ 重新获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// ⑥ 清理条件队列中的取消节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
// ⑦ 处理中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
5.2 await流程图

5.3 addConditionWaiter - 加入条件队列
java
private Node addConditionWaiter() {
Node t = lastWaiter;
// 清理已取消的节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建CONDITION状态的节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 加入队尾
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
5.4 fullyRelease - 完全释放锁
java
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 保存当前state(重入次数)
// 释放所有重入
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
为什么要完全释放?
因为await时必须让出锁,否则其他线程无法获取锁来调用signal。保存state是为了await返回后恢复原来的重入次数。
5.5 isOnSyncQueue - 检查是否在同步队列
java
final boolean isOnSyncQueue(Node node) {
// CONDITION状态或prev为null,肯定不在同步队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 有next,肯定在同步队列
if (node.next != null)
return true;
// 从tail向前搜索
return findNodeFromTail(node);
}
6. signal核心流程
6.1 signal方法源码
java
public final void signal() {
// ① 检查当前线程是否持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// ② 获取条件队列首节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
6.2 doSignal - 执行唤醒
java
private void doSignal(Node first) {
do {
// 移除首节点
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// 转移节点,如果失败则尝试下一个
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
6.3 transferForSignal - 转移到同步队列
java
final boolean transferForSignal(Node node) {
// ① 将节点状态从CONDITION改为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false; // 节点已取消
// ② 加入同步队列尾部
Node p = enq(node);
// ③ 设置前驱状态为SIGNAL,确保能被唤醒
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread); // 前驱取消或CAS失败,直接唤醒
return true;
}
6.4 signal流程图

6.5 signalAll - 唤醒所有
java
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignalAll(Node first) {
// 清空条件队列
lastWaiter = firstWaiter = null;
// 逐个转移所有节点
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
7. 有界阻塞队列
7.1 设计思路
使用两个Condition实现生产者-消费者模式:

7.2 完整实现
java
/**
* 基于ReentrantLock和Condition的有界阻塞队列
* @param <E> 元素类型
*/
public class BoundedBlockingQueue<E> {
/** 底层存储 */
private final Object[] items;
/** 队列容量 */
private final int capacity;
/** 当前元素数量 */
private int count;
/** 下一个put的位置 */
private int putIndex;
/** 下一个take的位置 */
private int takeIndex;
/** 主锁 */
private final ReentrantLock lock;
/** 非空条件:消费者等待 */
private final Condition notEmpty;
/** 非满条件:生产者等待 */
private final Condition notFull;
/**
* 构造方法
* @param capacity 队列容量
*/
public BoundedBlockingQueue(int capacity) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.capacity = capacity;
this.items = new Object[capacity];
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.notFull = lock.newCondition();
}
/**
* 放入元素(阻塞)
*/
public void put(E element) throws InterruptedException {
if (element == null)
throw new NullPointerException();
lock.lockInterruptibly();
try {
// 队列满时,等待notFull条件
while (count == capacity) {
notFull.await();
}
// 放入元素
enqueue(element);
} finally {
lock.unlock();
}
}
/**
* 取出元素(阻塞)
*/
public E take() throws InterruptedException {
lock.lockInterruptibly();
try {
// 队列空时,等待notEmpty条件
while (count == 0) {
notEmpty.await();
}
// 取出元素
return dequeue();
} finally {
lock.unlock();
}
}
/**
* 放入元素(超时)
*/
public boolean offer(E element, long timeout, TimeUnit unit)
throws InterruptedException {
if (element == null)
throw new NullPointerException();
long nanos = unit.toNanos(timeout);
lock.lockInterruptibly();
try {
while (count == capacity) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(element);
return true;
} finally {
lock.unlock();
}
}
/**
* 取出元素(超时)
*/
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
/**
* 入队(内部方法)
*/
private void enqueue(E element) {
items[putIndex] = element;
if (++putIndex == capacity)
putIndex = 0; // 循环数组
count++;
notEmpty.signal(); // 通知消费者
}
/**
* 出队(内部方法)
*/
@SuppressWarnings("unchecked")
private E dequeue() {
E element = (E) items[takeIndex];
items[takeIndex] = null; // help GC
if (++takeIndex == capacity)
takeIndex = 0; // 循环数组
count--;
notFull.signal(); // 通知生产者
return element;
}
/**
* 获取当前元素数量
*/
public int size() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
/**
* 判断队列是否为空
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* 判断队列是否已满
*/
public boolean isFull() {
return size() == capacity;
}
}
7.3 使用示例
java
public class BlockingQueueDemo {
public static void main(String[] args) {
BoundedBlockingQueue<Integer> queue = new BoundedBlockingQueue<>(5);
// 生产者线程
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("生产: " + i + ", 队列大小: " + queue.size());
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Producer");
// 消费者线程
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
Integer item = queue.take();
System.out.println("消费: " + item + ", 队列大小: " + queue.size());
Thread.sleep(200); // 消费慢于生产
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Consumer");
producer.start();
consumer.start();
}
}
7.4 与ArrayBlockingQueue对比
我们实现的BoundedBlockingQueue与JDK的ArrayBlockingQueue设计思路一致:
java
// ArrayBlockingQueue源码(简化)
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
final Object[] items;
int takeIndex;
int putIndex;
int count;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
// 实现与我们的几乎相同...
}
第五章:ReentrantLock 实践
1. 常见陷阱与解决方案
1.1 陷阱一:忘记释放锁
java
/**
* 错误:没有在finally中释放锁
*/
public void dangerous() {
lock.lock();
doSomething(); // 如果这里抛异常,锁永远不会释放!
lock.unlock();
}
/**
* 正确:使用try-finally
*/
public void safe() {
lock.lock();
try {
doSomething();
} finally {
lock.unlock();
}
}
1.2 陷阱二:lock()放在try块内
java
/**
* 错误:lock()在try内部
*/
public void wrongPosition() {
try {
lock.lock(); // 如果lock()之前就抛异常,finally会调用unlock()
doSomething();
} finally {
lock.unlock(); // 可能抛出IllegalMonitorStateException
}
}
/**
* 正确:lock()在try之前
*/
public void correctPosition() {
lock.lock(); // lock()在try之前
try {
doSomething();
} finally {
lock.unlock();
}
}
1.3 陷阱三:重入次数不匹配
java
/**
* 错误:lock和unlock次数不匹配
*/
public void mismatch() {
lock.lock();
lock.lock(); // 重入,state = 2
try {
doSomething();
} finally {
lock.unlock(); // 只释放一次,state = 1,锁未完全释放!
}
}
/**
* 正确:每次lock都对应一次unlock
*/
public void match() {
lock.lock();
try {
lock.lock();
try {
doSomething();
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
1.4 陷阱四:Condition.await()用if而非while
java
/**
* 错误:使用if检查条件
*/
public void wrongAwait() throws InterruptedException {
lock.lock();
try {
if (!ready) { // 可能虚假唤醒
condition.await();
}
// ready可能仍为false!
process();
} finally {
lock.unlock();
}
}
/**
* 正确:使用while循环
*/
public void correctAwait() throws InterruptedException {
lock.lock();
try {
while (!ready) { // 循环检查
condition.await();
}
// ready一定为true
process();
} finally {
lock.unlock();
}
}
虚假唤醒(Spurious Wakeup):线程可能在没有被signal()的情况下醒来,必须用while循环重新检查条件。
1.5 陷阱五:在错误的线程调用signal
java
/**
* 错误:不持有锁时调用signal
*/
public void wrongSignal() {
// 没有获取锁
condition.signal(); // 抛出IllegalMonitorStateException
}
/**
* 正确:必须在持有锁时调用
*/
public void correctSignal() {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
1.6 陷阱六:tryLock返回值未检查
java
/**
* 错误:忽略tryLock返回值
*/
public void ignoreTryLock() {
lock.tryLock(); // 返回值被忽略
try {
doSomething(); // 可能没有持有锁!
} finally {
lock.unlock(); // 可能抛出IllegalMonitorStateException
}
}
/**
* 正确:检查返回值
*/
public void checkTryLock() {
if (lock.tryLock()) {
try {
doSomething();
} finally {
lock.unlock();
}
}
}
2. 死锁预防与检测
2.1 死锁的四个必要条件

2.2 死锁示例
java
/**
* 典型死锁场景
*/
public class DeadlockDemo {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
// 线程1执行
public void method1() {
lockA.lock();
try {
Thread.sleep(100); // 模拟耗时
lockB.lock(); // 等待lockB
try {
// ...
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
// 线程2执行
public void method2() {
lockB.lock();
try {
Thread.sleep(100); // 模拟耗时
lockA.lock(); // 等待lockA,形成死锁!
try {
// ...
} finally {
lockA.unlock();
}
} finally {
lockB.unlock();
}
}
}
2.3 死锁预防策略
避免死锁我通常会从"三板斧"入手:统一加锁顺序、及时释放锁、配合超时/中断机制 。首先,多把锁同时使用时应该约定全局的加锁顺序,比如总是先锁 A 再锁 B,所有代码都遵守这个次序,从根源上破坏死锁的"循环等待"条件。其次,务必用 try { ... } finally { lock.unlock(); } 保证锁一定被释放,避免异常导致锁永远不解。再者,在一些复杂场景下,我会使用 tryLock(long timeout, TimeUnit unit) 或 lockInterruptibly():拿不到锁时可以超时退出或者响应中断,从而"自救",而不是无限期等待。此外,线上排查时可以结合 jstack 或 ThreadMXBean 的检测方法去发现和定位潜在死锁。
策略一:固定加锁顺序
java
/**
* 按固定顺序获取锁
*/
public class FixedOrder {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
// 所有方法都按A->B顺序获取锁
public void method1() {
lockA.lock();
try {
lockB.lock();
try {
// ...
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
public void method2() {
lockA.lock(); // 同样先A后B
try {
lockB.lock();
try {
// ...
} finally {
lockB.unlock();
}
} finally {
lockA.unlock();
}
}
}
策略二:使用tryLock超时
java
/**
* 使用tryLock避免死锁
*/
public boolean safeTransfer(Account from, Account to, int amount) {
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (true) {
if (from.lock.tryLock()) {
try {
if (to.lock.tryLock()) {
try {
// 转账操作
from.balance -= amount;
to.balance += amount;
return true;
} finally {
to.lock.unlock();
}
}
} finally {
from.lock.unlock();
}
}
// 检查超时
if (System.nanoTime() >= deadline) {
return false; // 超时失败
}
// 短暂休眠后重试
Thread.yield();
}
}
策略三:使用lockInterruptibly
java
/**
* 可中断的获取锁,支持取消
*/
public void interruptibleMethod() throws InterruptedException {
lock.lockInterruptibly();
try {
// ...
} finally {
lock.unlock();
}
}
// 检测到死锁时,可以中断线程来打破死锁
thread.interrupt();
2.4 死锁检测工具
使用jstack检测死锁:
bash
# 获取线程转储
jstack <pid>
编程方式检测:
java
/**
* 使用ThreadMXBean检测死锁
*/
public class DeadlockDetector {
private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
public void detectDeadlock() {
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
System.err.println("检测到死锁!涉及的线程:");
for (ThreadInfo info : threadInfos) {
System.err.println(info.getThreadName() +
" 等待锁: " + info.getLockName() +
" 持有者: " + info.getLockOwnerName());
}
}
}
// 定期检测
public void startMonitor() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::detectDeadlock, 10, 10, TimeUnit.SECONDS);
}
}
3. 性能优化建议
3.1 减小锁粒度
java
/**
* 粗粒度锁:整个方法加锁
*/
public class CoarseGrained {
private final ReentrantLock lock = new ReentrantLock();
private Map<String, Object> cache = new HashMap<>();
public Object getData(String key) {
lock.lock();
try {
Object data = cache.get(key);
if (data == null) {
data = loadFromDB(key); // 耗时IO操作也被锁住
cache.put(key, data);
}
return data;
} finally {
lock.unlock();
}
}
}
/**
* 细粒度锁:只锁必要部分
*/
public class FineGrained {
private final ReentrantLock lock = new ReentrantLock();
private Map<String, Object> cache = new HashMap<>();
public Object getData(String key) {
// 先检查缓存(需要锁)
lock.lock();
try {
Object data = cache.get(key);
if (data != null) {
return data;
}
} finally {
lock.unlock();
}
// 缓存未命中,加载数据(不需要锁)
Object data = loadFromDB(key);
// 更新缓存(需要锁)
lock.lock();
try {
cache.putIfAbsent(key, data);
return cache.get(key);
} finally {
lock.unlock();
}
}
}
3.2 减少锁持有时间
java
/**
* 长时间持有锁
*/
public void longHold() {
lock.lock();
try {
prepareData(); // 准备数据(不需要锁)
processData(); // 处理数据(需要锁)
saveResult(); // 保存结果(不需要锁)
} finally {
lock.unlock();
}
}
/**
* 只在必要时持有锁
*/
public void shortHold() {
Object data = prepareData(); // 准备数据(锁外)
lock.lock();
try {
processData(data); // 处理数据(锁内)
} finally {
lock.unlock();
}
saveResult(); // 保存结果(锁外)
}
3.3 使用读写锁
java
/**
* 读多写少场景使用ReadWriteLock
*/
public class ReadWriteOptimization {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private Map<String, Object> data = new HashMap<>();
// 读操作:多线程可并发
public Object read(String key) {
readLock.lock();
try {
return data.get(key);
} finally {
readLock.unlock();
}
}
// 写操作:独占
public void write(String key, Object value) {
writeLock.lock();
try {
data.put(key, value);
} finally {
writeLock.unlock();
}
}
}
3.4 避免锁竞争
java
/**
* 使用ThreadLocal避免锁竞争
*/
public class ThreadLocalOptimization {
// 每个线程有自己的SimpleDateFormat,无需同步
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String format(Date date) {
return dateFormat.get().format(date); // 无锁
}
}
3.5 公平锁vs非公平锁选择
java
/**
* 性能对比
*/
public class FairnessPerformance {
public static void main(String[] args) throws InterruptedException {
int threadCount = 10;
int iterations = 100000;
// 非公平锁测试
long unfairTime = testLock(new ReentrantLock(false), threadCount, iterations);
System.out.println("非公平锁耗时: " + unfairTime + "ms");
// 公平锁测试
long fairTime = testLock(new ReentrantLock(true), threadCount, iterations);
System.out.println("公平锁耗时: " + fairTime + "ms");
// 通常公平锁会慢很多
}
private static long testLock(ReentrantLock lock, int threads, int iterations)
throws InterruptedException {
// 测试代码...
return 0;
}
}
结论:非公平锁通常比公平锁快很多(可能快10倍以上),除非有特殊公平性要求,否则使用默认的非公平锁。
4. 锁选型指南
4.1 锁机制对比
| 锁机制 | 特点 | 适用场景 |
|---|---|---|
| synchronized | 简单、自动释放、JVM优化 | 简单同步,低竞争 |
| ReentrantLock | 功能丰富、灵活 | 需要高级特性 |
| ReadWriteLock | 读写分离 | 读多写少 |
| StampedLock | 乐观读、高性能 | 读多写少,追求性能 |
| AtomicXxx | 无锁CAS | 简单原子操作 |
4.2 选型决策树

4.3 ReentrantLock vs StampedLock
java
/**
* StampedLock示例(JDK 8+)
*/
public class StampedLockExample {
private final StampedLock sl = new StampedLock();
private double x, y;
// 写锁
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
// 乐观读(无锁)
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 获取乐观读标记
double currentX = x, currentY = y;
if (!sl.validate(stamp)) { // 检查是否被写入
// 乐观读失败,升级为悲观读
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
StampedLock的优势:
- 乐观读不阻塞写
- 读多写少时性能更好
StampedLock的限制:
- 不可重入
- 不支持Condition
- 使用更复杂
5. JDK中ReentrantLock的应用
5.1 典型应用场景
java
/**
* JDK中使用ReentrantLock的类
*/
// 1. ArrayBlockingQueue - 有界阻塞队列
public class ArrayBlockingQueue<E> {
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
// 使用ReentrantLock实现put/take的阻塞等待
}
// 2. LinkedBlockingQueue - 链表阻塞队列
public class LinkedBlockingQueue<E> {
private final ReentrantLock takeLock = new ReentrantLock();
private final ReentrantLock putLock = new ReentrantLock();
// 双锁提高并发度
}
// 3. ThreadPoolExecutor - 线程池
public class ThreadPoolExecutor {
private final ReentrantLock mainLock = new ReentrantLock();
// 保护workers集合的访问
}
// 4. CyclicBarrier - 循环屏障
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
// 使用Condition实现等待/唤醒
}
// 5. PrintWriter (某些实现)
// 6. StampedLock (内部使用)
5.2 为什么这些类选择ReentrantLock
| 类 | 选择ReentrantLock的原因 |
|---|---|
| ArrayBlockingQueue | 需要多个Condition(notEmpty, notFull) |
| LinkedBlockingQueue | 需要分离的锁提高并发 |
| ThreadPoolExecutor | 需要tryLock避免死锁 |
| CyclicBarrier | 需要Condition实现等待 |
6. 序列化与分布式
6.1 ReentrantLock不可序列化
java
/**
* ReentrantLock的序列化问题
*/
public class SerializationIssue {
// ReentrantLock没有实现Serializable接口
// private final ReentrantLock lock = new ReentrantLock();
/**
* 如果类需要序列化,锁字段需要特殊处理
*/
public class SerializableWithLock implements Serializable {
private static final long serialVersionUID = 1L;
// 使用transient,不序列化锁
private transient ReentrantLock lock = new ReentrantLock();
private int data;
// 反序列化时重新创建锁
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
lock = new ReentrantLock(); // 重新创建
}
public void increment() {
lock.lock();
try {
data++;
} finally {
lock.unlock();
}
}
}
}
为什么ReentrantLock不可序列化?
- 锁状态是运行时状态:序列化后再反序列化,原来的锁状态没有意义
- 线程绑定:锁与特定的线程关联,序列化后线程已不存在
- 设计选择:Doug Lea认为锁不应该被序列化
6.2 分布式环境的线程安全
ReentrantLock只能保证单JVM内的线程安全,分布式环境需要分布式锁:
java
/**
* 分布式环境下的锁方案
*/
public class DistributedLockOptions {
/**
* 方案1:基于Redis的分布式锁
*/
public void redisLock() {
// 使用Redisson
// RLock lock = redissonClient.getLock("myLock");
// lock.lock();
// try { ... } finally { lock.unlock(); }
}
/**
* 方案2:基于ZooKeeper的分布式锁
*/
public void zookeeperLock() {
// 使用Curator
// InterProcessMutex lock = new InterProcessMutex(client, "/locks/myLock");
// lock.acquire();
// try { ... } finally { lock.release(); }
}
/**
* 方案3:基于数据库的分布式锁
*/
public void databaseLock() {
// SELECT ... FOR UPDATE
// 或使用乐观锁(版本号)
}
}
分布式锁 vs JVM锁:
| 特性 | ReentrantLock | 分布式锁 |
|---|---|---|
| 作用范围 | 单JVM | 跨JVM/跨机器 |
| 性能 | 纳秒级 | 毫秒级 |
| 可靠性 | JVM崩溃锁丢失 | 可设置过期时间 |
| 复杂度 | 简单 | 需要额外中间件 |
| 可重入 | 支持 | 取决于实现 |
总结
又是没有大厂约面日子😣😣😣,小编还在找实习的路上,这篇文章是我的笔记汇总整理。