
开篇语
如何保障程序有序的运行,锁是永远都绕不开的一条拦路虎。即可以让程序有条不紊的运行,也可以让程序步履蹒跚。而今天,我们就来剥开 Java 中最常用的锁 ReentrantLock,研究它,参悟它。
文章结构
- 介绍 ReentrantLock 的简单使用,并快速学习 ReentrantLock 如何使用 AQS 实现同步器
- 详细剖析 CLH
- AQS 如何改造 CLH
- 介绍 CAS
- AQS 内部数据结构
- AQS 源码解析
- AQS 细节深入讨论
ReentrantLock 使用与解析
简单使用
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
// 加锁
reentrantLock.lock();
// 同步代码块
System.out.println("获取锁成功");
// 释放锁
reentrantLock.unlock();
}
}
复制代码
ReentrantLock 的使用非常简单,只需要创建一个 ReentrantLock 对象,获取锁调用 lock()方法,释放锁调用 unlock()方法。
与 Synchronized 在使用上的不同点:
- Synchronized 自动释放锁,ReentrantLock 手动释放锁
- Synchronized 可以使用于方法和代码块,ReentrantLock 只能使用于代码块
解析
ReentrantLock 并不直接继承 AbstractQueuedSynchronizer 类,而是定义了一个子类 Sync 去继承 AbstractQueuedSynchronizer,让子类去实现同步器。
ReentrantLock 实现了 Lock 接口,该接口定义了一个锁需要实现的方法。ReentrantLock 实现 Lock 接口中的方法都是通过 Sync 类中的方法来完成。

ReentrantLock 内部使用了一个抽象内部类 Sync 继承了 AbstractQueuedSynchronizer 类。NonfairSync 类继承 Sync,用于提供非公平锁的功能;FairSync 类继承 Sync,用于提供公平锁的功能。
public class ReentrantLock implements Lock, Serializable {
// 同步器
private final Sync sync;
// 内部抽象类
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
@ReservedStackAccess
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()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
/**
* 实现非公平锁的类
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 实现公平锁的类
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
@ReservedStackAccess
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;
}
}
复制代码
ReentrantLock 实现了 lock() 方法,暴露给外部获取锁;实现了 unlock()方法,暴露给外部释放锁。
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
复制代码
我们可以看到这两个方法实际上都是调用了 Sync 类的方法,因此 ReentrantLock 本身并没有实现同步器的功能,而是交给内部类 Sync 类实现。ReentrantLock 通过调用 Sync 提供的同步方法实现了同步器的功能。
CAS
CAS 是 compareAndSwap 的缩写,中文译为:比较和交换。是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令,这个指令会对内存中的共享数据做原子读写操作。其作用是让CPU 比较内存中某个值是否和预期的值相同,如果相同则将这个值更新为新值,不相同则不做更新,以此达到同步的效果。
CAS 在 Java 中的使用非常多, java.util.concurrent.atomic 这个包下的类都是使用 CAS 来实现的。
示例
| 内存地址 | 0x01 | 0x02 | 0x03 |
|---|---|---|---|
| 值 | 1 | 2 | 3 |
我们假设在内存 0x01 处存放 1,0x02 处存放 2,0x03 处存放 3。现在我们有一个线程 A,它要使用 CAS 操作修改 0x01 处的值为 4。此时,它只需要这样操作即可。
给 CAS 函数提供三个参数:内存地址,内存地址中存放的原始值,内存地址需要存放的新值。函数原型 cas(*address, oldValue, newValue)。
更新成功
线程 A 执行 cas(0x01, 1, 4),系统会先取出内存地址为 0x01 的值,取出值为1,与传入的旧值 1 相同,此时就会将 4 写入到内存地址 0x01 处,内存变为:
| 内存地址 | 0x01 | 0x02 | 0x03 |
|---|---|---|---|
| 值 | 4 | 2 | 3 |
更新失败
如果在线程 A 执行cas(0x01, 1, 4)时,存在另外一个线程 B,它先于线程 A 修改了 0x01 处的值,它将值改为

文章介绍了Java中ReentrantLock的使用,通过其内部的Sync类和AQS(AbstractQueuedSynchronizer)实现同步器。ReentrantLock通过lock()和unlock()方法调用Sync类中的方法来实现锁的获取和释放。AQS是基于CLH(CliffordHall自旋锁)改进的,使用了队列来保证公平性,避免锁饥饿。文章还探讨了CAS操作以及CLH锁的原理和Java实现,以及AQS如何改造CLH以解决自旋锁的一些问题,最后分析了AQS的acquire和release方法实现。
最低0.47元/天 解锁文章
452

被折叠的 条评论
为什么被折叠?



