引言:
上一章,我们学习了AQS同步队列的使用,了解了同步队列如何进行线程同步及线程唤醒,及同步队列的运行。这章,让我们一起来看下同步队列的时间应用--ReentrantLock(可重入锁)类
本章基于上一章AQS的内容实现,如果对AQS有不理解,请先阅读上一章内容
简介:
可重入锁,表示同一线程如果已经抢占到锁,那么可以直接重复进入被锁的代码而不需要做额外的操作。
ReentrantLock不仅是可重入锁,且支持公平锁与非公平锁两种方式,公平锁表示新加入的线程无视同步队列的存在而抢占锁,非公平锁则需要先进入同步队列等待,让同步队列中的线程先抢占锁。
实现分析:
1,同步状态值
ReentrantLock锁的实现,首先基于AQS中的同步状态值state,该值表示锁被重入的次数,如果锁未被抢占,state=0,重入一次则state+1。
/**
* The synchronization state.
*/
private volatile int state;
2,ReentrantLock的初始化实现
如下代码所示,默认ReentrantLock是实现非公平锁,只有传入true构造时,才能创建公平锁。
/**
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3,AQS的虚拟子类的实现
如下图所示,ReentrantLock中先定义了一个虚拟子类,定义了非公平锁加锁及释放锁的方法。
3.1 非公平锁nonfairTryAcquire方法
步骤1,获取当前同步器同步状态c
步骤2(如果锁未被抢占,c=0),则直接抢占锁
步骤3(如果锁已被抢占但是当前线程所占),将c+抢占次数,并更新同步器同步状态。
步骤4,非以上两种情况,则直接返回失败。
3.2 释放锁tryRelease公用方法
步骤1,获取同步器同步状态c=当前状态值-释放锁次数
步骤2,如果c=0,表示锁完全释放,则返回true,否则返回false。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
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;
}
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;
}
}
如上代码所示,ReentrantLock类使用AQS类中的同步状态判断可重入锁的状态。
4,非公平锁的特殊实现
可以看出,非公平锁锁定时,直接使用CAS方法试图抢占锁,成功则更新为锁所有线程,失败调用 acquire 进入同步器处理,在同步器处理中再次调用tryAcquire尝试获取锁。后续详细步骤见上一章。
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
5,公平锁的特殊实现
如下图所示,公平锁实现了自己的tryAcquire方法,与非公平锁的最主要区别在于公平锁当前线程只有同步队列为空,同步队列中当前线程在头节点时,才可以直接抢占锁,否则需要进入同步队列进行排队。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
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类的主要内容,其实了解了AQS之后,这块内容就比较轻松了。
整个Concurrent包中的Lock已经差不多了,明天是最后一篇读写重入锁,后续开始学习Concurrent包中其他内容
Concurrent包
感兴趣的同学们请关注本人公众号:暖爸的java家园