上个月入职新公司,忙的一直没时间抽出时间来写文章,有点惭愧,今天晚上感觉挤出一些时间来写一篇文章。
显式锁的实现其实是基于AQS同步器,所以阅读本篇文章之前,建议先理解AQS同步器的原理。可以看我之前写的一篇文章:
Java同步器框架-AQS原理&源码解析
Sync
ReentrantLock内部定义了一个抽象类Sync,实现了具体的tryRelease()
方法,但没有tryAcquire()
方法的实现。后面又定义了Sync的子类FairSync和NonfairSync两个类,分别代表两种策略,一种是公平的方式去获取锁,一种以非公平的方式获取锁。
我们先看一下公共的tryRelease()
方法实现
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;
}
tryRelease()
方法很简单,主要判断释放资源后state是否为0,如果为0就返回true,接下来就是AQS的内部工作了,会唤醒正在阻塞的线程。
FairSync和NonfairSync的不同主要体现在tryAcquire()
方法,我们来看下他们各自的实现
FairSync
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;
}
}
//如果当前线程和占用锁的线程是同一个线程,就可以继续进入
//ReentrantLock的可重用性就是通过这个来体现的
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
从上面的代码可以看出,FairSync会很优雅的去看一下前面有没有人在排队,没有人排队的话才去尝试获取锁(不一定能获取成功,因为可能同时有其他人也才尝试获取锁,失败的人就会进入队列排队)。
NonfairSync
final void lock() {
//先尝试直接设置state状态,设置成功了说明锁获得成功,其他线程看到state!=0就会进入休眠
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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;
}
上面的代码可以看出,无论是lock方法还是nonfairTryAcquire方法,NonfairSync都是先尝试设置state状态为1,只要通过cas设置成功了,就说明成功获取到锁了。我们可以看nonfairTryAcquire这个方法,NonfairSync和FairSync最大的区别就是没有!hasQueuedPredecessors()
这一步判断,也就是在这个策略里面,锁申请者会先直接尝试获取锁,由于占用锁的线程释放锁到通知CLH队列的第一个排队者来获取是有个时间差的,非公平模式的锁申请者可能在这个时间差里强行插队获取到锁,这就造成了不公平的现象,违反了先到先得的规则。
ReentrantLock的一些方法实现
理解了NonfairSync和FairSync,再来看ReentrantLock里面一些常用的方法。
public void lock() {
sync.lock();
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public void unlock() {
sync.release(1);
}
我们可以看到常用的lock()
方法,tryLock()
方法,unlock()
方法,实现都很简单,都是直接调用sync的方法。sync的实现策略决定了lock的获取锁的策略不同。
比较有意思的是,tryLock()调用的始终是不公平的nonfairTryAcquire()
方法,与sync的具体实现策略无关。
在nonfairTryAcquire()
方法中,只要能把state设置成1或者可重入就说明获取到锁了,然后结果也会返回true,否则就返回false。和tryLock()的语义类似。
其他的方法也基本都是基于Sync和AQS一些原有的实现,理解了AQS后再来看这些方法就一目了然了,这里就不细讲了。
Condition
ReentrantLock 里面有个newCondition()
方法,也是调用sync的方法
public Condition newCondition() {
return sync.newCondition();
}
//ReentrantLock.Sync
final ConditionObject newCondition() {
return new ConditionObject();
}
ConditionObject是AQS的一个内部类,它实现了Condition类,所以相应的实现了await(),signal()
等方法。
当对某个线程执行ConditionObject的await()方法进入休眠的时候,会释放该线程所持有的AQS的资源,从而让其他的线程可以得到资源。
同理,当执行signal()唤醒某个线程时,会先尝试获取锁,获取失败的话也和正常的线程一样进入AQS的CLH队列中等到。
在 CyclicBarrier
类中就用到了ReentrantLock的newCondition()
方法。
总结
ReentrantLock也是基于AQS实现的,所以理解了AQS后再来看ReentrantLock的实现就很简单了。再理清楚公平锁和非公平锁的实现策略,就可以很清晰的了解它的整个原理了。
如果哪里有写的不对的地方,烦请指出,感激不尽!
网友评论