ReentrantLock :可重入锁
参考链接: https://www.bilibili.com/video/BV1ta4y1H73X
需要具备 AQS 知识
可重入指的是单个线程执行时重新进入同一个子程序仍是线程安全的。
如果是不可重入,若 A 获得锁,要再次请求该锁时就会造成死锁
简单来说,就是一个线程可以不用释放即可重复获得该锁 n 次,释放时响应释放 n 次。
那下面就来讲讲 RenentrantLocak 这一可重入锁的实现。
首先来看一下 RenentrantLocak 的继承关系,其实现了 Lock 接口,即遵循 Lock 接口的抽象定义。
public class ReentrantLock implements Lock, java.io.Serializable
Lock
Lock 提供了区别于 synchronized 的另一种同步操作方式。
操作更广泛,能支持更多灵活的结构,并且可以关联多个 Condition 对象。
// 获取锁
void lock();
// 获取锁,若在等待锁的过程中被打断,则退出等待并抛出异常
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,并立即返回
boolean tryLock();
// 在一段时间内尝试获取锁,如果期间被中断,则会抛出中断异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 新建一个绑定在当前 Lock 上的 Condition 对象
Condition newCondition();
公平锁 与 非公平锁
公平锁:按照锁的请求顺序进行分配,如 AQS 中的 FIFO 机制实现的就是公平锁
非公平锁:不按锁的请求顺序进行分配,不过这种方式可能存在 饥饿 现象
为什么设计非公平锁呢?
实际上,非公平锁的效率往往更高,可能能提高并发的性能。
当唤醒线程时,线程状态切换会有短暂的延时,非公平锁机制允许线程在这段事件进行锁的抢占,快速处理内容。
这是非公平锁比公平锁性能更好的原因之一。
构造方法
从构造方法上看,ReentrantLock 实际上是 Sync 类,可以通过参数对 FairSync 和 NonfairSync(默认)进行选择。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
属性
final Sync sync
Sync 是 ReentrantLock 内部抽象类,继承了 AQS.
NonfairSync 和 FairSync 是 Sync 实现子类,分别是对 非公平锁 和 公平锁 的实现。
abstract static class Sync extends AbstractQueuedSynchronizer {...}
static final class NonfairSync extends Sync {...}
static final class FairSync extends Sync {...}
回到 Sync, 留意到 Sync 中存在nonfairTryAcquire(int acquires)
方法,通过名称可以明白其是非公平锁的方法,既然由 NonfairSync 和 FairSync 的实现,为何该方法会在其父类中定义呢?
下面就来看看该方法:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 0 表示锁状态空闲,则可以进行 CAS 来获取锁
// 若 state 更改成功,则获取到该锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 若锁被占用,判断当前线程是否是持有锁的线程
else if (current == getExclusiveOwnerThread()) {
// 重入,则给计数 +1
int nextc = c + acquires;
if (nextc < 0) // overflow,即防止线程数量溢出后变为负数
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 未获取到锁
return false;
}
NonfairSync
NonfairSync 通过提供两次抢占实现非公平锁机制:
- lock 时进行抢占
- 重写的 tryAcquire 调用的 nonfairTryAcquire
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 尝试对锁进行获取 [第一次抢占]
if (compareAndSetState(0, 1))
// 成功则获取到锁
setExclusiveOwnerThread(Thread.currentThread());
else
// 失败则调用 AQS 的 acquire 方法
// 由于 acquire 方法中调用的 tryAcquire 被重写
// 因此是调用下方的 trayAcquire 方法 [第二次抢占]
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 直接进行入队操作
acquire(1);
}
// 重写 tryAcquire
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;
}
}
方法
lock
在 sync 中已实现,直接调用即可。
public void lock() {
sync.lock();
}
trylock
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
unlock
public void unlock() {
sync.release(1);
}