ReentrantLock源码解读

217 阅读6分钟

1.介绍

ReentrantLock可重入锁是我们考虑同步时,经常选择的一个方案,在jdk很多类的源码如ConcurrentHashMap、ThreadPoolExecutor等都有使用。相比于synchronized其有如下几个优势

1)等待可中断 :lockInterruptily()方法

2)等待可设置超时,一定时间拿不到锁直接返回 :tryLock(long timeout,TimeUnit unit)方法

3)可绑定多个条件 :newCondition()方法,可多次调用得到不同的condition,对相应的条件执行await,signal方法,更加灵活

4)可实现公平锁\非公平锁

2.源码分析

ReentrantLock是AbstractQueuedSynchronizer的子类,AbstractQueuedSynchronizer使用了模板方法,通过重写下面5个方法中的一个或多个以达到自己想要的同步效果。

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、NonFairSync和FairSync,其中NonFairSync和FairSync都是Sync的子类,NonFairSync是非公平锁实现,FairSync是公平锁实现。下面介绍公平锁和非公平锁在实现过程中的差异。

2.1 FairSync和NonFairSync的lock()方法

FairSync的lock()方法:
final void lock() {
    acquire(1);//此处调用超类AbstractQueuedSynchronizer的acquire()方法
}

NonFairSync的lock()方法
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

不同点:
对于非公平锁,想象如下场景:当前有一个线程持有锁,并且正在执行业务逻辑,有多个线程等待该线程执行结束,
然后分配锁资源给其中一个。在持有锁的线程业务执行完成释放锁之后,此时持有锁的线程数量为0,即state=0,
此时如果刚好有新的线程调用lock()方法,其通过CAS发现state=0,可以直接获取到锁,然后将持有锁的线程设置为
最近调用lock()的方法,因为还存在等待锁的线程,就将锁分配给了新来的线程,显然这种情况锁是非公平的。

对于公平锁:即使state=0,也需要调用FairSync的acquire()方法去竞争锁(主要通过FairSync的tryAcquire()来保证,
将在后面展开)

2.2 AbstractQuenedSynchronizer的acquire(int arg)方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//如果获取不到锁,则将自己加入等待队列的尾部
        selfInterrupt();
}

因为公平锁的tryAcquire()方法涉及到判断等待队列中,因此先介绍其他方法

2.3 AbstractQueuedSynchronizer的addWaiter(Node mode)方法;

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//新建Node结点,其内部保存了当前线程,方便后面通过LockSupport.unpark(trhead)来换新阻塞的线程
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {//如果当前已经有阻塞的线程,则将本node链接到队列的尾部
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);//如果当前还没有阻塞的线程或者上面链接失败,则调用enq将当前node填入队列尾部
    return node;
}

2.4 AbstractQueuedSynchronizer的enq(final Node node)方法

private Node enq(final Node node) {
    for (;;) {//如果失败则一直重试
        Node t = tail;
        if (t == null) { // Must initialize
            //如果队列中还没有结点则新建一个结点,并将头指针和尾指针指向它,注意:头结点和尾结点并不保存
            线程等其他信息,真正有效的等待结点是头尾结点中间的结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {//将要入队的结点添加到队列的尾部,更新尾结点为当前入队的结点
                t.next = node;
                return t;
            }
        }
    }
}

2.5 AbstractQueuedSynchronizer的acquireQueue(final Node node,int arg)方法

final boolean acquireQueued(final Node node, int arg) {//返回在调用本方法期间当前线程的中点标记是否曾经被设置位true,只要有一次被设置为true,则最终返回true
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {//head结点只是一个标记,因此当本线程是第一个等待的node时,尝试获取锁,如果获取到锁则将
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&//若当前结点不是等待队列的第一个结点或者上边是第一个等待结点尝试获取锁失败则进入shouldParkAfterFailedAcquire()方法
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)//整体看下来只会在tryAcquire(arg)中可能抛出异常,比如锁重入的次数太多,或者一次性申请的资源过大,导致getState()+acquires大于Integer.MAX_VALUE
            cancelAcquire(node);//将当前的node的状态置为Node.CANCELLED
    }
}

2.6 AbstractQueuedSynchronizer的shouldParkAfterFailedAcquire(Node pred,Node node)方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;//如果前一个结点为Node.SINGAL状态,直接返回true,执行后边的LockSupport.park()方法
        这里需要跟ReentraktLock.unlock()方法中的unparkSuccessor(Node node)结合起来看
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);//如果上一个结点状态为CANCELLED,则动态的将前边连续的状态都为CANCELLED的结点删掉
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//通过CAS将上一个结点状态置为SIGNAL
    }
    return false;//如果执行到这里直接返回false,需要重新回到acquireQueued中for循环
}

2.7 AbstractQueuedSynchronizer的parkAndCheckInterrupted()

通常调用lock()方法,如果获取不到锁都是阻塞在这个地方

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//阻塞当前线程,这里使用无参的park()方法也可以,不知道是出于什么考虑
    return Thread.interrupted();//返回当前线程的中点标记是否为true,同时将中断标记置为false
}

2.8 FairSync和NonFairSync的tryAcquire(int acquire)方法

FairSync类
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&//比较之后可以看出公平锁和非公平锁唯一区别就是公平锁尝试获取锁会先查看等待队列中是否有线程在等待锁,如果1)没有线程等待获取该锁2)等待线程中的第一个线程即为当前线程,因为ReentrantLock是可重入锁,则将该锁分配给当前线程
            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;
}

NonFairSync类
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;
}

2.9 AbstractQueuedSynchronizer的hasQueuedPredecessors()方法

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&//如果head==tail说明等待队列为空,直接返回false
        ((s = h.next) == null || s.thread != Thread.currentThread());//(s=h.next)==null个人认为不会发生
        可能是代码有地方看漏了,第二个判断是比较当前线程是否与等待队列中的第一个结点的线程是否相等,如果
        相等,则直接返回false
}

2.10 ReentrantLock的unlock()方法

public void unlock() {
    sync.release(1);//直接调用的是AbstractQueuedSynchronized的release(int arg)方法,即公平锁和非公平锁的释放锁逻辑一致
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)//如果后面又等待结点,其前一个结点状态一定不是0,可以将这里跟shoudParkAfterFailedAcquire结合起来看
            unparkSuccessor(h);//唤醒等待队列中的第一个结点
        return true;
    }
    return false;
}

2.11 AbstractQueuedSynchronizer的unparkSuccessor(Node node)方法

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {//如果node后的第一个等待结点为null或者状态为cancelled,那么需要找到后续第一个等待状态小于0的结点的结点。
    
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//将该等待线程唤醒
}