前言
Java的Lock机制是Java并发编程(JDK 1.5+)中用于控制多个线程访问共享资源的核心工具。它位于java.util.concurrent.locks包下,提供了比传统的synchronized关键字更灵活、更强大的锁定操作。
本文主要介绍以 ReentrantLock 和 ReentrantReadWriteLock 为代表的lock机制,文章内容包含一下几个:Java Lock 类继承关系、Lock 使用代码示例、Lock 原理。
类结构
Java的lock机制源头可以从Lock接口说起,这是lock机制的抽象类,规定了锁的相关协议,它定义了锁的获取和释放方法。相比于synchronized(隐式获取/释放锁),Lock需要显式地获取和释放锁。
Lock接口核心方法:
public interface Lock {
// 1. 基本锁操作
void lock();
void unlock();
// 2. 可中断锁
void lockInterruptibly() throws InterruptedException;
// 3. 尝试获取锁
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 4. 条件变量
Condition newCondition();
}
Java同步机制里面涉及的接口层主要有下面三个核心接口:
- Lock 接口 - 所有锁的顶级接口,定义了 lock()、unlock() 等基本方法
- ReadWriteLock 接口 - 读写锁接口,定义了读锁和写锁的获取方法
- Condition 接口 - 条件变量接口,用于线程间的等待/通知机制
对上面几个接口类常用的主要有几个实现类:
- ReentrantLock:实现了 Lock 接口内部包含 Sync 同步器
- ReentrantReadWriteLock:实现了 ReadWriteLock 接口 内部包含 Sync、ReadLock、WriteLock几个内部类
- ConditionObject:实现了 Condition 接口,是 AQS 的内部类
简化版继承结构类图UML:
flowchart TD
%% 接口层
Lock[Lock接口] --> RL[ReentrantLock]
Lock --> RLock[ReadLock]
Lock --> WLock[WriteLock]
RWL[ReadWriteLock接口] --> RWLImpl[ReentrantReadWriteLock]
RWLImpl --> RLock
RWLImpl --> WLock
Cond[Condition接口] --> CondObj[ConditionObject]
%% AQS层
AQS[AQS] --> Sync[Sync]
AQS --> RWSync[ReadWriteSync]
Sync --> FSync[FairSync]
Sync --> NFSync[NonfairSync]
%% 组合关系
RL -.-> Sync
RWLImpl -.-> RWSync
RLock -.-> RWSync
WLock -.-> RWSync
Sync -.-> CondObj
RWSync -.-> CondObj
style Lock fill:#f9f
style RWL fill:#f9f
style Cond fill:#f9f
style AQS fill:#ccf
style RL fill:#cfc
style RWLImpl fill:#cfc
style RLock fill:#cfc
style WLock fill:#cfc
使用方式
ReentrantLock 使用示例
package concurrent;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
// 1. 基本 lock() 和 unlock()
public void basicLock() {
lock.lock();
try {
counter++;
System.out.println(Thread.currentThread().getName() +
" [basicLock] 计数器: " + counter);
Thread.sleep(100); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
/** 持锁一段时间,用于配合 lockInterruptibly 演示 */
public void holdLock(long millis) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 持有锁 " + millis + "ms");
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
}
// 2. tryLock(timeout) - 超时尝试获取锁
public void tryLockWithTimeout() {
try {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
System.out.println(Thread.currentThread().getName() +
" [tryLockTimeout] 在500ms内获取成功");
Thread.sleep(200); // 模拟工作
} finally {
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() +
" [tryLockTimeout] 获取超时");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +
" [tryLockTimeout] 被中断");
Thread.currentThread().interrupt();
}
}
// 3. lockInterruptibly() - 可中断锁(在等待锁的过程中可被 interrupt 并抛出 InterruptedException)
public void lockInterruptiblyExample() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " 尝试 lockInterruptibly(),若锁被占用将阻塞等待...");
lock.lockInterruptibly(); // 阻塞等待期间若被 interrupt,会抛出 InterruptedException
try {
System.out.println(Thread.currentThread().getName() + " 获取锁成功,执行业务");
Thread.sleep(500);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放锁");
}
}
}
// 运行 ReentrantLock 所有示例
public static void main(String[] args) throws Exception {
System.out.println("\n========== ReentrantLock 示例开始 ==========");
ReentrantLockDemo reentrantDemo = new ReentrantLockDemo();
// 1. 基本 lock() 示例
System.out.println("\n1. 基本 lock() 示例:");
Thread t1 = new Thread(() -> reentrantDemo.basicLock(), "Thread-1");
Thread t2 = new Thread(() -> reentrantDemo.basicLock(), "Thread-2");
t1.start();
t2.start();
t1.join();
t2.join();
// 2. tryLock(timeout) 示例
System.out.println("\n2. tryLock(timeout) 示例:");
Thread t5 = new Thread(() -> reentrantDemo.tryLockWithTimeout(), "Thread-5");
Thread t6 = new Thread(() -> reentrantDemo.tryLockWithTimeout(), "Thread-6");
t5.start();
t6.start();
t5.join();
t6.join();
// 3. lockInterruptibly() 示例:先让一个线程持锁,另一个线程在 lockInterruptibly() 上阻塞,再中断阻塞线程
System.out.println("\n3. lockInterruptibly() 示例(在等待锁时被中断):");
Thread holder = new Thread(() -> reentrantDemo.holdLock(5000), "Holder");
Thread interruptible = new Thread(() -> {
try {
reentrantDemo.lockInterruptiblyExample();
System.out.println("Interruptible-Thread 正常结束");
} catch (InterruptedException e) {
System.out.println("Interruptible-Thread 在等待锁时被中断,抛出 InterruptedException");
Thread.currentThread().interrupt();
}
}, "Interruptible-Thread");
holder.start();
Thread.sleep(100); // 确保 Holder 先拿到锁
interruptible.start();
Thread.sleep(800); // 此时 Interruptible 正在 lockInterruptibly() 上阻塞
interruptible.interrupt(); // 中断正在等待锁的线程
interruptible.join();
holder.join();
System.out.println("\n========== ReentrantLock 示例结束 ==========");
}
}
代码运行结果:
========== ReentrantLock 示例开始 ==========
1. 基本 lock() 示例:
Thread-1 [basicLock] 计数器: 1
Thread-2 [basicLock] 计数器: 2
2. tryLock(timeout) 示例:
Thread-5 [tryLockTimeout] 在500ms内获取成功
Thread-6 [tryLockTimeout] 在500ms内获取成功
3. lockInterruptibly() 示例(在等待锁时被中断):
Holder 持有锁 5000ms
Interruptible-Thread 尝试 lockInterruptibly(),若锁被占用将阻塞等待...
Interruptible-Thread 在等待锁时被中断,抛出 InterruptedException
Holder 释放锁
========== ReentrantLock 示例结束 ==========
ReentrantLock 使用示例
package concurrent;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReentrantReadWriteLock 使用示例:
* - 读锁(readLock):共享,多线程可同时持有,与写锁互斥
* - 写锁(writeLock):独占,同一时刻仅一个线程可持有,与读锁、写锁均互斥
*/
public class ReentrantReadWriteLockDemo {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private String data = "initial";
// 1. 读锁:多个线程可同时读
public void readWithReadLock() {
System.out.println(Thread.currentThread().getName() + " 尝试获取读锁");
readLock.lock(); // 若写锁被占用,会在此阻塞
try {
System.out.println(Thread.currentThread().getName() + " 获取到读锁(写锁已释放后才会执行到这里)");
System.out.println(Thread.currentThread().getName() + " [读锁] 读取: " + data);
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
readLock.unlock();
}
}
// 2. 写锁:独占,同一时刻只有一个线程可写
public void writeWithWriteLock(String newData) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " [写锁] 写入: " + newData);
data = newData;
Thread.sleep(300);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writeLock.unlock();
}
}
// 3. 持写锁一段时间,用于演示读/写互斥
public void holdWriteLock(long millis) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 持有写锁 " + millis + "ms(此时读锁会阻塞)");
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 先打印再 unlock,保证控制台顺序能体现:先释放写锁,后读者才拿到读锁
System.out.println(Thread.currentThread().getName() + " 释放写锁");
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
System.out.println("========== 1. 读锁共享:多个线程同时读 ==========");
Thread r1 = new Thread(() -> demo.readWithReadLock(), "Reader-1");
Thread r2 = new Thread(() -> demo.readWithReadLock(), "Reader-2");
Thread r3 = new Thread(() -> demo.readWithReadLock(), "Reader-3");
r1.start();
r2.start();
r3.start();
r1.join();
r2.join();
r3.join();
System.out.println("\n========== 2. 写锁独占:同一时刻只有一个写 ==========");
Thread w1 = new Thread(() -> demo.writeWithWriteLock("A"), "Writer-1");
Thread w2 = new Thread(() -> demo.writeWithWriteLock("B"), "Writer-2");
w1.start();
w2.start();
w1.join();
w2.join();
System.out.println("\n========== 3. 读与写互斥:写锁持有时,读锁阻塞 ==========");
Thread holder = new Thread(() -> demo.holdWriteLock(2000), "Writer-Holder");
Thread reader = new Thread(() -> demo.readWithReadLock(), "Reader-Blocked");
holder.start();
Thread.sleep(100);
reader.start();
holder.join();
reader.join();
System.out.println("\n========== ReentrantReadWriteLock 示例结束 ==========");
}
}
代码运行结果:
========== 1. 读锁共享:多个线程同时读 ==========
Reader-1 尝试获取读锁
Reader-2 尝试获取读锁
Reader-3 尝试获取读锁
Reader-1 获取到读锁(写锁已释放后才会执行到这里)
Reader-2 获取到读锁(写锁已释放后才会执行到这里)
Reader-3 获取到读锁(写锁已释放后才会执行到这里)
Reader-1 [读锁] 读取: initial
Reader-3 [读锁] 读取: initial
Reader-2 [读锁] 读取: initial
========== 2. 写锁独占:同一时刻只有一个写 ==========
Writer-1 [写锁] 写入: A
Writer-2 [写锁] 写入: B
========== 3. 读与写互斥:写锁持有时,读锁阻塞 ==========
Writer-Holder 持有写锁 2000ms(此时读锁会阻塞)
Reader-Blocked 尝试获取读锁
Writer-Holder 释放写锁
Reader-Blocked 获取到读锁(写锁已释放后才会执行到这里)
Reader-Blocked [读锁] 读取: B
========== ReentrantReadWriteLock 示例结束 ==========
Lock 原理
Java lock机制使用AQS来管理锁的状态,进而管理同步锁的获取和释放。
使用AQS管理锁状态
AQS (AbstractQueuedSynchronizer) 是一个抽象接口,核心代码如下:
// AQS 简化实现原理
public abstract class AbstractQueuedSynchronizer {
// 核心字段1: 同步状态(32位int)
private volatile int state;
// 核心字段2: CLH队列(双向链表)
private transient volatile Node head; // 队列头
private transient volatile Node tail; // 队列尾
// 队列节点
static final class Node {
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 等待的线程
volatile int waitStatus; // 等待状态
Node nextWaiter; // 条件队列使用
}
// 核心方法:CAS操作
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
}
ReentrantLock 使用 AQS 的 state 字段表示锁状态,类内部存在一个Sync内部类:
// ReentrantLock 使用 AQS 的 state 字段表示锁状态
public class ReentrantLock {
abstract static class Sync extends AbstractQueuedSynchronizer {
// state 含义:
// 0: 无锁状态
// 1: 有线程持有锁(非重入)
// N: 同一个线程重入了 N-1 次
}
}
另外值得一提的是这里的waitStatu,是指线程的等待状态,我简单列举下方便后续查看:
| 状态常量 | 数值 | 英文全称 | 核心目的 | 主要使用场景 |
|---|---|---|---|---|
| CANCELLED | 1 | Cancelled | 标记节点已放弃等待 | 线程中断、超时、异常 |
| SIGNAL | -1 | Signal | 建立唤醒承诺 | 同步队列中等待唤醒 |
| CONDITION | -2 | Condition | 分离条件等待 | Condition条件队列 |
| PROPAGATE | -3 | Propagate | 优化共享锁传播 | 共享模式唤醒传播 |
| (默认) | 0 | Initial/None | 初始或特殊状态 | 新创建节点、已获取锁的节点 |
tips: SIGNAL 是"唤醒保证书",当前节点向它的后继节点承诺 "等我用完锁,一定会唤醒你"。
基于lock获取同步锁时,会经历以下流程:
graph TD
A[线程尝试获取锁] --> B{是否持有锁?}
B -->|是| C[重入计数+1]
B -->|否| D{state是否为0?}
D -->|是| E{公平锁?}
E -->|是| F{有前驱节点?}
F -->|无| G[CAS获取锁]
F -->|有| H[入队等待]
E -->|否| I[直接CAS尝试]
I -->|成功| J[获取成功]
I -->|失败| H
D -->|否| H
G --> K[获取成功]
H --> L[进入CLH队列]
L --> M[自旋或挂起]
M --> N[被唤醒后尝试]
这里内部类Sync实现主要有公平锁和非公平锁两种实现,本文不展开深入,可看下篇文章。另外,这里涉及到的CLH 队列是一种自旋锁的等待队列实现,全称为 Craig, Landin, and Hagersten queue,是 AQS(AbstractQueuedSynchronizer)中实现线程排队等待的核心数据结构。
核心实现 acquire 方法
这里调用lock方法获取锁的时候,会有条核心调用链路:
sequenceDiagram
box rgb(240, 248, 255) Thread Flow
participant T as 用户线程
participant RL as ReentrantLock
participant Sync as Sync/AQS
participant Q as AQS队列
end
Note over T,RL: 步骤1: 用户调用lock()
T->>RL: lock()
RL->>Sync: sync.lock()
Note over Sync,RL: 步骤2: 具体实现取决于公平/非公平<br>这里以公平锁FairSync为例
Sync->>Sync: FairSync.lock()
Sync->>Sync: acquire(1)
Note over Sync,Q: 步骤3: AQS核心获取逻辑
rect rgb(255, 250, 240)
Sync->>Sync: !tryAcquire(1) &&<br>acquireQueued(addWaiter(...), 1)
end
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
步骤1: tryAcquire(arg)
目的:快速尝试获取
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; // 获取失败
}
步骤2: addWaiter(Node.EXCLUSIVE)
目的:进行排队等待
private Node addWaiter(Node mode) {
// 创建新节点(模式为独占模式)
Node node = new Node(Thread.currentThread(), mode);
// 快速尝试:直接CAS添加到队尾
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)) { // CAS设置尾节点
t.next = node;
return t;
}
}
}
}
步骤3: acquireQueued(node, arg)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; // 标记是否获取失败
try {
boolean interrupted = false; // 标记是否被中断
for (;;) { // 自旋循环
final Node p = node.predecessor(); // 获取前驱节点
// 关键条件:前驱节点是头节点,尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node); // 获取成功,设置为新头节点
p.next = null; // 帮助GC,断开旧头节点
failed = false;
return interrupted; // 返回中断状态
}
// 判断是否需要挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // 挂起线程
interrupted = true; // 记录中断状态
}
} finally {
if (failed)
cancelAcquire(node); // 获取失败,取消节点
}
}
其中 shouldParkAfterFailedAcquire(pred, node) 逻辑如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) // 前驱节点状态为SIGNAL,可以安全挂起
return true;
if (ws > 0) { // 前驱节点已取消(CANCELLED)
do {
node.prev = pred = pred.prev; // 跳过已取消的节点
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置前驱节点状态为SIGNAL(需要唤醒我)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
parkAndCheckInterrupt() 逻辑如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 挂起当前线程
return Thread.interrupted(); // 返回中断状态并清除中断标记
}
读写锁获取的实现
读写锁这里用到了state字段做了些状态设计:
static class Sync extends AbstractQueuedSynchronizer {
// 使用 state 的高16位表示读锁数量,低16位表示写锁重入次数
// state = (读锁数量 << 16) | 写锁重入次数
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读锁单位: 65536
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 最大计数: 65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 写锁掩码: 0xFFFF
// 获取读锁数量
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 获取写锁重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
写锁获取的核心方法如下:
// WriteLock 的 tryAcquire 方法
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 获取写锁数量
if (c != 0) { // 有锁被持有
// 情况1: 有读锁 (w == 0 但 c != 0)
// 情况2: 有写锁但不是当前线程持有 (w != 0 && 持有者 != current)
if (w == 0 || current != getExclusiveOwnerThread())
return false; // 获取失败
// 情况3: 当前线程持有写锁(重入)
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
// c == 0 无锁状态
if (writerShouldBlock() || // 公平性检查
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
读锁获取核心方法如下:
// ReadLock 的 tryAcquireShared 方法
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果有写锁,并且不是当前线程持有的
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1; // 获取失败
int r = sharedCount(c); // 当前读锁数量
// 检查是否应该阻塞(公平性检查)
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 第一个读锁
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
}
// 当前线程是第一个读锁持有者
else if (firstReader == current) {
firstReaderHoldCount++;
}
// 其他线程持有读锁
else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1; // 获取成功
}
// CAS失败或应该阻塞,进入完整获取流程
return fullTryAcquireShared(current);
}