概述
上一篇看了AbstractQueuedSynchronizer的源码,了解AQS以后,写一个锁就非常简单了。分析ReentrantLock前先看一个简单实现的不可重入的独占锁的例子。
public class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
if(getExclusiveOwnerThread() != Thread.currentThread()) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
可以看到写一个独占锁类非常简单,实现Lock接口,内部定义一个队列同步器,重写tryAcquire和tryRelease就好了。所以本篇分析的重入锁也是类似。

功能:1.可重入 2.不公平的锁(默认机制) 3.公平锁
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以看到ReentrantLock构造函数中也新建了Sync对象,默认是NonfairSync非公平同步器。那我们先分析非公平锁的实现
//ReentrantLock中的lock方法,可以看到就是调用sync
public void lock() {
sync.lock();
}
//NonfairSync中的lock方法
final void lock() {
//CAS尝试加锁, state从0设为1,表示加锁成功
if (compareAndSetState(0, 1))
//设置独占线程是自己,表明自己是这个锁的拥有者,好牛逼哈
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//否则进入AQS的获取锁的流程
}
既然是不公平的,作者就让他不公平到底,进来就进行一次CAS设置尝试获取锁,如果获取成功就将锁的拥有者线程设为自己。
内心OS:如果没有compareAndSetState这个上来就抢的强盗逻辑,后来的人都走acquire,不就是公平的了吗?进去排队,锁用完释放了,下一个顶上,但是我忽略的一点,线程用完释放了,可能再次获取锁,这样就会造成不公平,后面的线程产生饥饿现象。有兴趣的可以下载我多线程测试的例子。多线程测试github地址
好了,上面有点跑偏了,我们再回来看,如果加锁失败进入acquire。这个方法我们知道是AbstractQueuedSynchronizer类中的,方法内部都是上一篇分析过的。tryAcquire尝试获取锁的方法需要子类重写。
//AQS类中的acquire方法,具体源码分析,可看上一篇AbstractQueuedSynchronizer 源码分析
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//NonfairSync重写的方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//state为0表示锁被释放,现在可以去竞争了
if (c == 0) {
//再次CAS设值, CAS的设值问题,上篇简述过
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//state不为0,说明已有线程占用,判断是否是自己,所以这里是支持锁重入的
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;
}
如果tryAcquire获取成功就加锁完成,否则返回false,进入AQS队列,获取锁的流程。 加锁的代码就分析完了。这里需要注意一点,锁的重入state会继续累加,所以每个加锁都要释放。
unlock解锁
//ReentrantLock中的unlock方法
public void unlock() {
sync.release(1);
}
//调用的是AQS中的release方法
//该方法已分析过,
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//ReentrantLock中的tryRelease方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//只能释放自己占用的锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//当state值减为0说明锁完全释放,将占用线程设置为空
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
state值减去release,为0说明完全解锁,返回true。
因为可重入的原因state可能减去releases以后不为0,说明还被线程占用着,方法返回false。
公平锁FairSync
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//0 表示现在没有线程占用锁
//判断必须没有前继节点,然后CAS设置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;
}
- 公平锁的lock直接调用AQS类的acquire方法
- 重写的tryAcquire调用成功则占用,否则进去队列排队等待
- 可以看到tryAcquire方法中,当state等于0时,会判断必须没有前继节点(这就是实现公平锁的关键,保证必须排队,释放锁以后就要再次排队)
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
返回队列不为空且第二节点的线程不是自己时返回true,则不再尝试竞争。
可能有点绕,我们再分析一次,把外层方法tryAcquire中 if(!hasQueuedPredecessors())把"!"移到方法内部,则表达式变为h == t || ((s = h.next) != null && s.thread == Thread.currentThread()), 这个表达式更清楚一些,当等待队列为空或者当前节点式第二节点时,进行获取尝试获取锁。
队列为空很好理解,没有抢,肯定我抢嘛。为什么或者是第二节点呢?因为头节点是上次占用的节点,需要让出锁,这样保证了公平性。
公平锁的释放和非公平锁调用的是同一方法,因为篇幅原因,其他还有一些方法就不再分析了