ReenTrantLock是juc包下的一个经典的互斥锁,也是 可重入锁(即当前线程在已经获取改锁后重复执行获取锁操作时不会引起死锁,并且不需要执行获取锁的操作),ReenTrantLock是基于AQS来实现的(PS:注意是基于不是继承,看到网上有的同学会说发现ReentrantLock并没有继承AQS有所疑问),如果有对AQS不了解的同学可以查看我之前的文章浅析AQS(1)—独占锁以及共享锁的实现以及浅析AQS (二)–condition的实现,
首先ReenTrantLock是默认的非公平锁,但是也有公平锁的实现,需要在构造方法中传入是否公平的Boolean值来标志,需要注意的是,此处的公平与非公平只针对lock方法,而tryLock方法是指定非公平的.并不受限制
ReenTrantLock实现并发控制是依靠内部类Sync及其两个子类FairSync和NoFairSync,顾名思义这两个子类是提供公平锁以及非公平锁的实现,而Sync则是继承了AQS的具体实现类,接下来我们结合源码具体分析
首先查看一下Sync的源码
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//抽象lock方法 根据公平与非公平实现执行不同的操作
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;
}
// 是否持有当前锁
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
//新建condition对象
final ConditionObject newCondition() {
return new ConditionObject();
}
//获取锁当前的持有线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//当前线程获取到了
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
}
在Sync对象中,重写了tryRelease方法,将释放锁的逻辑统一,并且定义了nonfairTryAcquire非公平获取锁的方法,这里为什么要将该方法定义在父类是因为无论是公平还是非公平锁,tryLock方法中都是采用非公平获取的方式,而无论维护的Sync对象时公平的还是非公平的,我们都需要一个可以非公平获取锁的方式
在nonfairTryAcquire我们可以看到,如果当前锁的state为0即当前锁没人持有时则采用cas的方式获取锁,然后将持有现成设置为当前线程,而第二个判断则是可重入锁的实现,即判断持有锁的是不是当前线程,如果是则直接获取锁
关于ConditionObject的实现可以查看我以前的文章浅析AQS (二)–condition的实现,
下面放一下公平与非公平锁的具体实现
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
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;
}
在NoFairSync中调用lock方法直接尝试用cas的方式获取锁,并不去查看你是否有锁占用,直接尝试获取,如果成功则直接获取锁,如果失败则采用acquire的方式来获取
而在FairSync中实现的tryAcquire方法中与Sync类中的noFairTryAcquire基本一致,只是在没人获取锁时判断当前队列是否为空,或者当前线程是否在队列的最开始位置,总得来说就是判断是否有其他线程等待时长比当前线程要长,主要是为了保证获取锁的公平性
接下来我们看看ReenTrantLock是如何使用这两种锁的,首先来看看构造方法
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
这里指定了锁的公平与非公平,默认采用非公平锁,如需公平锁则传入一个true即可
接下来查看lock与tryLock方法
public void lock() {
sync.lock();
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
如同上文所说,在lock方法中是根据当前所公平与非公平来进行而tryLock方法则是固定以非公平的方式来进行调用
以上就是ReenTrantLock的具体实现方式,结合AQS的两篇文章即可将该类理解通透