ReentrantLock源码分析

236 阅读5分钟

概述

上一篇看了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就好了。所以本篇分析的重入锁也是类似。

上面是重入锁的类图,可以看到也是内部一个队列同步器Sync,由于要实现两个功能,所以Sync下有两个子类,NonfairSync不公平同步器,FairSync公平同步器。

功能: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;
}
  1. 公平锁的lock直接调用AQS类的acquire方法
  2. 重写的tryAcquire调用成功则占用,否则进去队列排队等待
  3. 可以看到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()), 这个表达式更清楚一些,当等待队列为空或者当前节点式第二节点时,进行获取尝试获取锁。
队列为空很好理解,没有抢,肯定我抢嘛。为什么或者是第二节点呢?因为头节点是上次占用的节点,需要让出锁,这样保证了公平性。

公平锁的释放和非公平锁调用的是同一方法,因为篇幅原因,其他还有一些方法就不再分析了