一、背景知识
了解 Java 中 ReentrantLock 的程序员都知道,ReentrantLock 的核心组成是队列同步器 AbstractQueuedSynchronizer,它采用的模板方法的设计模式,即使用者需要继承同步器并重写指定的方法,而同步器中的一些模板方法将会调用这些方法。需要自定义的方法有下面几个:
//独占式获取同步状态
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//独占式释放同步状态
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//共享式获取同步状态
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//共享式释放同步状态
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
//是否被当前线程独占
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
而在 ReentrantLock 类的内部,定义了一个抽象类 Sync,看代码:
public class ReentrantLock implements Lock {
...
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer
{...}
...
}
在 Sync 的内部有几个比较重要的方法:
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {...}
protected final boolean tryRelease(int releases) {...}
}
ReentrantLock 内部还定义了两个类并继承了抽象类 Sync,见代码。顾名思义,这两个类分别代表了 ReentrantLock 的两种锁模式:公平锁和非公平锁。
static final class NonfairSync extends Sync {...}
static final class FairSync extends Sync {...}
接下来分析 ReentrantLock 的公平锁和非公平锁。
二、非公平锁
ReentrantLock 默认情况下是构造非公平锁,但也提供了一个可供选择的构造函数,代码一目了然:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
加锁方式如下:
public void lock() {
//这里的lock()方法就是Sync内部类重定义的抽象方法,见上文。
sync.lock();
}
lock( ) 方法在子类 FairSync 和 NonfairSync 中实现了。先看非公平锁:
static final class NonfairSync extends Sync {
final void lock() {
//首先原子性设置同步状态
if (compareAndSetState(0, 1))
//成功的话将独自占有锁的线程设为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
//否则,执行模板方法 void acquire(int arg)
acquire(1);
}
//模板方法void acquire(int arg)中调用了boolean tryAcquire(int arg)方法
protected final boolean tryAcquire(int acquires) {
//nonfairTryAcquire(int arg)是在Sync抽象类中定义的
return nonfairTryAcquire(acquires);
}
}
模板方法的定义是固定好的:
public final void acquire(int arg) {
//尝试获取同步状态,失败的话添加节点到同步队列中
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
所以重点分析 Sync内部类中的 boolean nonfairTryAcquire(int 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()) {
//c不为0的话说明其他线程正在占有锁,此时判断是否为当前线程获取了锁
//是的话就可以获取同步状态,这也是可重入锁的关键
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//否则获取同步状态失败
return false;
}
比较一下公平锁的代码就只带差异了。
三、公平锁
直接上代码:
static final class FairSync extends Sync {
final void lock() {
//此处没有直接去设置state同步状态,而是调用了模板方法。
//模板方法调用了下面的boolean tryAcquire(int acquires)方法
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //差异在这里
/*当前没有其他线程获取到同步状态时,不是直接去设置state
*而是检查当前线程所在的节点是否有前驱节点(同步队列
*是一个FIFO队列,存在前驱节点的话说明有线程更早地
*尝试获取同步状态),没有前驱节点的话才去设置state
*/
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;
}
}
从代码可以看出公平锁和非公平锁的差异了。差异就是:在 compareAndSetState(0, acquires)) 的时候是否去检查有没有更早的获取锁的线程。公平锁秉承着“先到先得”的理念,而非公平锁则不管这么多,只要是当前 state 为0,就去尝试获取。
四、最后
ReentrantLock的精髓远不于此,要想理解好ReentrantLock,就必须掌握 AbstractQueuedSynchronizer,同步器的分析在后面。