美文网首页
ReentrantLock源码解析

ReentrantLock源码解析

作者: 雁阵惊寒_zhn | 来源:发表于2020-10-02 22:21 被阅读0次

    ReentrantLock编码示例

    Lock lock = new ReentrantLock();//①
    lock.lock();//②
    try {
        //do something
    } finally {
        lock.unlock();//③
    }
    

    ①new ReentrantLock()

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    

    无参数构造函数初始化一个非公平锁。关于公平锁和非公平锁,以及Java锁参考Java锁

    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {...}
    

    Sync类是一个内部类,继承实现的AQS锁框架(参考文档AQS)。
    在ReentrantLock中,Sync类又衍生出两个子类分别为NonfairSync类和FairSync类,也就是非公平锁和公平锁的实现。

    static final class NonfairSync extends Sync {...}
    static final class FairSync extends Sync {...}
    

    ②lock.lock()

    无参数构造函数初始化一个非公平锁。lock()函数底层调用类NonfairSync的lock函数。

    1. if语句:利用底层的CAS操作比较并且赋值AQS框架中的变量state值,如果设置成功(state由0到1),当前线程获得锁,并且设置为锁的owner。
    2. else语句:CAS操作失败,调用父类的acquire()函数获取锁,父类acquire()函数最终调用的就是子类NonfairSync的tryAcquire()函数。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        /**
        * Performs lock.  Try immediate barge, backing up to normal
        * acquire on failure.
        */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    

    类Sync中定义的非公平锁获取函数nonfairTryAcquire()。说明在代码注释中。

    final boolean nonfairTryAcquire(int acquires) {
        //获得当前的线程
        final Thread current = Thread.currentThread();
        //取得状态变量state的值
        int c = getState();
        //如果state的值是0,没有锁。
        if (c == 0) {
            //CAS操作设置state,尝试获得锁。
            //这里进行了第二次的比较(第一次在NonfairSync类的lock函数中)
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //当前线程已经获得锁
        else if (current == getExclusiveOwnerThread()) {
            //增加state
            int nextc = c + acquires;
            //如果结果小于0,意味着整型溢出
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //可以重入,设置state
            setState(nextc);
            return true;
        }
        return false;
    }
    

    如果获得锁,线程继续执行接下来的逻辑。那么没有获得锁呢?tryAcquire()返回false,未获得锁,执行的是acquireQueued()线程入队等待的逻辑。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

    线程未获得锁的情况下要入队等待。等待队列也是公平锁和非公平锁逻辑操作的差别所在,非公平锁是当前所有的线程都可以执行CAS操作尝试获得锁,而公平锁只有队列中第一个线程可以获得锁。

    //返回新的节点
    private Node addWaiter(Node mode) {
        //当前线程被封装为链表的Node节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //插入到尾节点之后
        Node pred = tail;
        //尾节点不为null,当前队列中已经存在等待的线程
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //初始化等待队列的第一个节点
        enq(node);
        return node;
    }
    
    //入队后的节点
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋尝试获得锁
            for (;;) {
                final Node p = node.predecessor();
                //如果线程位于队列的第一个位置,尝试获得锁。
                if (p == head && tryAcquire(arg)) {
                    //把自己从队列中去除
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //线程判断自己是否需要阻塞
                //当前线程节点的前驱信号设置为Node.SIGNAL,意味着自己将等待被唤醒。
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    总结一下,等待的线程节点会自旋再次尝试获得锁。如果仍旧没有成功,判断自己是否需要被阻塞,根据需要阻塞自己,等待其他线程唤醒自己后,继续尝试获得锁。

    ③lock.unlock()

    锁的释放过程与锁的获取过程刚好相反,怎么获得的就要怎么释放。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    

    第一步:尝试释放锁tryRelease()

    protected final boolean tryRelease(int releases) {
        //减少状态变量state
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //如果结果为0,没有线程持有锁
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        //更新state
        setState(c);
        return free;
    }
    

    第二步:释放成功后,唤醒等待队列中的后继线程unparkSuccessor()

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
        */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
        */
        Node s = node.next;
        //找到当前节点的第一个有效的后继节点
        if (s == null || s.waitStatus > 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);
    }
    

    最后,被唤醒的后继节点,执行上面介绍的acquireQueued()函数继续尝试获得锁。

    总结

    1. 初始化
    • 无参数构造函数初始化非公平锁
    • 可以传递参数设置公平锁
    1. 加锁
    • 没有获得锁的线程会进入等待队列。
    • 自旋技术,大多数的锁持有时间短暂,而且大多数线程竞争不严重。基于这样的推断,运用自旋技术等待一会,马上尝试可以减少线程阻塞和唤醒的消耗。
    • 如果迟迟得不到锁,线程会被挂起。
    1. 释放锁
    • 释放自己持有的锁
    • 唤醒等待队列中自己的后继线程

    相关文章

      网友评论

          本文标题:ReentrantLock源码解析

          本文链接:https://www.haomeiwen.com/subject/floeuktx.html