ReentrantLock
ReentrantLock类看名字叫可重入锁,具体的功能实现我们结合代码解读
ReentrantLock结构解读
实现接口Lock
研究一个类要从该类的结构入手,从一个类的继承实现,成员变量,构造方法,内部类,静态变量等
类的实现:实现了Lock和序列化
//实现了Lock接口 声明了序列化
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
//重写了Lock方法
public void lock() {
sync.lock();
}
..................................
}
因为实现了Lock接口,所以重写其接口方法,只是方法具体内部实现都委托给了sync
内部基类Sync ,内部类FairSync和NonfairSync
//提供所有实现机制的同步器
private final Sync sync;
代码看到 类Sync(同步器)的非访问修饰符是final,意味着 sync 对象一旦被创建并赋值,就不能再指向另一个对象。而访问修饰符是private 意味这Sysn只能在本类ReentrantLock 中使用
再来看内部类Sync的定义
// 抽象类继承AbstractQueuedSynchronizer (AQS)
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
......内部类方法体...............
}
static修饰:可独立于外部类实例存在,不依靠外部类引用,减少内存泄漏风险;
abstract修饰:抽象类不能被实例化,但可以作为基类让子类继承,实现特定的同步逻辑支持多态扩展
同时继承了AbstractQueuedSynchronizer(AQS),说明是依靠AQS大体上实现同步逻辑。
再来看具体继承基类Sync 实现实例化的两个子内部类
static final class NonfairSync extends Sync {
......NonfairSync 方法体...............
}
static final class FairSync extends Sync {
......FairSync方法体...............
}
继承基类Sync的类是另外两个内部类FairSync 和NonfairSync ,修饰符static final,静态常量化内部类,意思就是随着类加载就存在的两个类,且一旦创建就不在改变,提高安全度
构造方法
public class ReentrantLock implements Lock {
private final Sync sync;
// 构造方法:默认非公平锁,可指定公平策略
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// 核心方法委托给sync
public void lock() {
sync.lock(); }
public void unlock() {
sync.release(1); }
// 其他方法(如tryLock)略
}
两个构造器 默认是非公平锁,说明ReentrantLock 有两种模式非公平锁和公平锁,由于构造方法返回的是Sync说明ReentrantLock 实际实现是在Sync里面,而重写的Lock方法返回也是sync.xxx这样,下面我就开始解读Sync具体实现功能
基类Sync(同步器)实现的功能方法
继承AQS,又是静态 抽象的修饰符,上面已经介绍了这些作用,这里不复述,开始介绍方法体
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
}
1. lock() 方法
抽象类里的抽象方法,需要子类FairSync,NonfairSync 继承并且去实现具体逻辑
// 抽象方法:子类需实现锁的获取逻辑
abstract void lock();
2.nonfairTryAcquire(int acquires)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取同步状态(重入锁值)
int c = getState();
//判断锁未被占用(state == 0)
if (c == 0) {
// 直接尝试获取锁,不检查队列
if (compareAndSetState(0, acquires)) {
//设置当前拥有独占访问权限的线程
setExclusiveOwnerThread(current);
return true;
}
}
//判断 锁已被当前线程占用(重入:增加重入次数)
else if (current == getExclusiveOwnerThread()) {
// 重入逻辑:增加重入次数 理论上一般acquires通常是1
int nextc = c + acquires;
if (nextc < 0) // 若重入次数溢出(nextc < 0)抛出错误
throw new Error("Maximum lock count exceeded");
//否则更新state并返回true
//无需同步:由于只有持有锁的线程能执行此逻辑,直接使用setState而非CAS,确保线程安全
setState(nextc);
return true;
}
//若锁被其他线程占用,或CAS竞争失败,返回false,触发线程加入等待队列
return false;
}
总结:方法被final修饰 代表该方法不能被重写,非公平性体现:不管队列先不排队,新线程可直接插队尝试获取锁。
逻辑解析:
①判断锁未被占用(state == 0)
Ⅰ.直接尝试CAS获取锁:通过compareAndSetState(0, acquires)尝试将锁状态从0修改为1(acquires通常为1)。
Ⅱ.若成功,则设置当前线程为锁的独占所有者,返回true。
②判断锁已被当前线程占用(重入):
Ⅰ.若当前线程已是锁的所有者,增加重入次数:将state增加acquires(通常是1)。若重入次数溢出(nextc < 0),抛出错误;否则更新state并返回true。
Ⅱ.无需同步:由于只有持有锁的线程能执行此逻辑,直接使用setState而非CAS,确保线程安全。
③获取失败:
若锁被其他线程占用,或CAS竞争失败,返回false,触发线程加入等待队列。
3.tryRelease()
protected final boolean tryRelease(int releases) {
//通过 int c = getState() - releases 计算释放后的状态值
int c = getState() - releases;
//线程合法性检查:检查当前线程是否是持有独占锁的线程:
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//若 c == 0,表示锁完全释放,将独占线程置为 null,并标记 free = true。
//否则若 c > 0,表示仍处于重入状态,锁未完全释放,free 保持 false。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//无论是否完全释放,都通过 setState(c) 更新同步状态。这确保了重入场景下状态逐步递减
setState(c);
//返回 free,告知上层(如 release() 方法)是否需要唤醒后续线程。
return free;
}
这又是一个final修饰的方法 代表不能被重写,同时这个方法有向上引用,是AQS里面方法的重写实现,这是AQS里面钩子方法tryRelease的其中一个实现
总结:
①通过计算释放锁的值释放归零 判断锁是否完全释放,所以重入锁多少次 就需要释放锁多少次,是一种原子性清零,当然也可以一次性释放,比如重入锁是3次直接传参释放3次,ConditionObject里面就是这么做的。无论是否完全释放都通过 setState ( c ) 更新同步状态
②里面有线程合法性检查,由于只有持有锁的线程能调用此方法,无需额外同步。所以这个方法是线程安全的
4.isHeldExclusively()
是一个用于判断当前线程是否独占持有锁的方法
//通过 getExclusiveOwnerThread() 获取当前持有锁的线程,并与当前线程比较
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
//getExclusiveOwnerThread方法
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
//exclusiveOwnerThread变量值
private transient Thread exclusiveOwnerThread;
这又是一个final修饰的方法 代表不能被重写,同时这个方法有向上引用,是AQS里面方法的重写实现,这是AQS里面钩子方法isHeldExclusively的其中一个实现
检查独占性:方法实现内容:确认当前线程是否是锁的独占持有者(即是否持有锁)。
支持条件变量:在 Condition 的实现中(如 await() 和 signal()),需要确保调用线程持有锁,此时会调用此方法进行验证
5.newCondition()
类中本地方法 没重写其余类或接口方法,有final说明不可以被重写,
final ConditionObject newCondition() {
return new ConditionObject();
}
1.return new ConditionObject()返回ConditionObject 实例,创建条件变量 支持线程的精确等待唤醒
2.每个ConditionObject 必须与一个独占锁(如 ReentrantLock)配合使用,只有持有锁的线程才能调用其 await()、signal() 等方法
3.可以多次调用 newCondition() 创建多个独立的 Condition 对象
6.getOwner()
getOwner() 方法的作用是获取当前持有独占锁的线程(即锁的拥有者)
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
通过 state 和 exclusiveOwnerThread 的双重判断,确保方法的返回值符合锁的实际状态:
若 state(同步状态)为 0,表示锁未被任何线程持有,返回 null。
当 state > 0 时,锁已被持有,getExclusiveOwnerThread必然指向有效线程。返回当前独占线程exclusiveOwnerThread
7.getHoldCount()
getHoldCount() 方法的作用是获取当前线程对锁的重入次数(即锁被同一线程重复获取的次数)
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
若当前线程独占持有锁(通过 isHeldExclusively() 判断),返回 state 的值,表示锁的重入次数 + 1(例如 state=3 表示线程重入了 2 次)。
若当前线程未持有锁,直接返回 0
8.isLocked()
isLocked() 方法的作用是快速判断锁是否被占用(无论持有者是谁)
final boolean isLocked() {
return getState() != 0;
}
锁占用状态检查
若 state(同步状态)不等于 0,表示锁被某线程持有(可能是当前线程或其他线程),返回 true。
若 state 等于 0,表示锁未被任何线程持有,返回 false
private volatile int state;仅通过读取 volatile 变量 state 完成,无锁竞争,性能高效。
与 getOwner() 的差异,isLocked():仅检查锁是否被占用(state != 0),而getOwner():返回持有锁的具体线程(需结合 state 判断)。
9.readObject
在反序列化时重置锁状态,确保反序列化后的对象处于未锁定状态
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();// 默认反序列化流程
setState(0); // 强制将锁状态重置为未锁定
}
反序列化安全性
锁的持有状态(state 和 exclusiveOwnerThread)与运行时线程上下文紧密相关。序列化时可能捕获到一个被锁定的状态,但反序列化时原持有线程已不存在,导致锁永久不可用。通过强制重置状态setState(0) ,确保反序列化后的锁处于未锁定的初始状态。
内部类 非公平锁NonfairSync
该内部类继承了抽象类Sync 同时又作为ReentrantLock构造方法的默认返回类型,代码如下
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 重写了sync的lock方法
final void lock() {
//使用CAS操作将state从0改为1。如果成功,说明当前线程获取了锁
if (compareAndSetState(0, 1))
//设置当前线程为独占拥有者
setExclusiveOwnerThread(Thread.currentThread());
else
//这是AQS的核心方法,用于处理锁获取失败后的排队和重试逻辑
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
非公平锁有两个方法,先看tryAcquire方法,该方法是AQS里面的钩子方法tryAcquire重写的,其返回值代表尝试获取锁成功或失败,具体实现是在Sync类中nonfairTryAcquire方法,该方法已经在上面阐述了 这里不在复述
lock方法逻辑步骤
①使用CAS操作将state从0改为1。如果成功,说明当前线程获取了锁
②如果CAS操作成功设置当前线程为独占拥有者
③如果CAS操作失败,那就再去acquire方法试试,acquire是AQS核心方法之一,有意思的是acquire方法也是先尝试获取锁,获取失败了(tryAcquire(arg)==false)才加入同步队列(addWaiter),然后不断重试获取锁(acquireQueued)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
内部类 公平锁FairSync
公平锁:该内部类继承了抽象类Sync ,是ReentrantLock构造方法的返回类型之一
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
//参数:acquires 表示请求的锁数量(通常为1)。
protected final boolean tryAcquire(int acquires) {
//步骤1:获取当前线程与锁状态c
final Thread current = Thread.currentThread();
int c = getState();
//处理锁未被持有的情况(c == 0)
if (c == 0) {
// 公平性检查:是否有更早的等待线程?
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断确认当前线程已经是锁的持有者,允许重入
else if (current == getExclusiveOwnerThread()) {
//更新重入次数:计算新的重入次数(如原 state=2,acquires=1,则 nextc=3)
int nextc = c + acquires;
//溢出检查:若 nextc < 0(如 state=Integer.MAX_VALUE 时加1导致溢出)
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//更新状态:setState(nextc) 直接设置新值(无需CAS,因为只有持有线程能修改)。
setState(nextc);
return true;
}
return false;
}
}
老规矩先看tryAcquire方法,这里tryAcquire方法自行实现了功能逻辑
步骤1:先获取当前线程和同步状态
步骤2:同步状态==0判断是否当前锁未被线程持有,那就做公平性检查:是否有更早的等待线程
条件1:!hasQueuedPredecessors():检查同步队列中是否有比当前线程更早等待的线程,是AQS的方法通过检查头尾节点判断队列状态
条件2:compareAndSetState(0, acquires):CAS操作将 state 从 0 更新为 acquires(通常为1),独占锁模式下 同一时刻只有一个线程能成功修改状态
如果条件1和2都满足 说明没有更早的等待线程,获取锁也能行,那就设置当前线程为独占持有者 直接返回ture表示锁获取成功
步骤3:如果锁已被当前线程持有 那这次获取锁相当于重入一次,处理下重入更新下同步状态
公平锁的公平体现在锁未被线程持有情况下,先判断是否有更早的等待线程
再来看重写的lock方法 比较简单,其核心依然是AQS的acquire方法,但区别于非公平锁的是,acquire方法里面的tryAcquire方法调用的公平锁的tryAcquire方法,构造器用的哪个内部类锁类型,对应的AQS方法最终调用的是哪个内部类锁类型的方法