可重入锁ReentrantLock源码分析

987 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


写在前面

本文主要是针对 ReentrantLock 实现 AQS 的基础上的分析以及对 Condition 的相关分析。

因此建议先了解AQS的实现原理,对 ReentrantLock 原理的理解将更加容易,AQS 详情点击

一、起源:

1. 什么是可重入锁?

可重入锁是指当某个线程已经持有了这把锁,但是某个时刻,这个线程还要尝试再次拿到这把锁,支持这种可重入的实现就是可重入锁;

以 ReentrantLock 可重入锁来看,其 state 表示重入次数,当想要再次拿到这把锁的时候,state + 1

当想要释放这把锁的时候state - 1,因此可以根据 state 是否等于 0 来判断这把锁是否被某个线程锁持有。

2. ReentrantLock的基本用法

先创建 ReentrantLock 对象,然后 lock.lock(),最后 lock.unlock(),即:

     class X {
       private final ReentrantLock lock = new ReentrantLock();
       // ...
        public void m() {
         lock.lock();  // block until condition holds
         try {
           // ... method body
         } finally {
           lock.unlock()
         }
       }
     }}

使用上比较简单,推荐的做法是在 finally 块中释放锁,因为这样在出现异常的时候可以及时释放锁资源

3. ReentrantLock如何实现等待/通知模式?

关键字 synchronized 与 wait() 和 notify() / notifyAll() 方法相结合可以实现等待/通知模式,ReentrantLock 也同样可以借助于 Condition 实现等待/通知模式

  • Condition 是 JDK1.5 中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个 lock 对象里可以创建多个 Condition (即对象监视器)实例,线程对象可以注册在指定的 Condition 中,从而可以有选择性的进行线程通知,在调度线程上更加灵活

  • 在使用 notify()/notifyAll() 方法进行通知时,被通知的线程却是由 JVM 随机选择的;但使用 ReentrantLock 结合 Condition 是可以实现"选择性通知"

在了解 ReentrantLock 实现 AQS 之前,先来看看 AQS 的实现类一般如何去操作...

4. AQS用法

一般要通过 AQS 实现自己的同步器类有以下三步:

  • 新建自己的同步器类,在内部写一个 Sync类,该类继承AbstractQueuedSynchronizer,即 AQS
  • 设计同步器类的逻辑,在Sync类里,根据是否独占来重新对应的方法。如果是独占,则重写 tryAcquire 和 tryRelease 等方法;如果是非独占,则重写 tryAcquireShared 和 tryReleaseShared 等方法
  • 在自己的同步器类中实现获取/释放相关方法,并在里面调用 AQS 对应的方法,如果是独占则调用 acquire 或 release 等方法,非独占则调用 acquireShared 或 releaseShared 或 acquireSharedInterruptibly 等方法。

二、实现原理

需要注意的的是,state 在 ReentrantLock 的含义表示的是重入次数

  • state = 0,表示此锁未被任何线程持有
  • state > 0, 表示被当前线程重入持有多次,以后每次释放锁都会在 state 上减 1,直到 state = 0

ReentrantLock 是一种独占模式,相应的会实现 tryAcquire 和 tryRelease 方法。

在 Condition 中有两个名词需要做区分

  • 条件等待队列,这个队列是由每个 condition 实例自己维护的,也就是说,如果有两个 condition 实例,也就有两个条件等待队列
  • 同步队列:指的是 AQS 中的 FIFO

等待与唤醒:

  • condition.await:等待操作是将节点放入条件等待队列
  • condition.signal:唤醒操作是将节点从条件等待队列中移到同步队列中,等待获取资源

另外,ReentrantLock 大部分实现都是由 AQS 完成,在上篇博文中已经对 AQS 做了详细分析,因此这里不在过多重复分析...

1. 内部对象Sync实现AQS

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        abstract void lock();

        // 非公平锁,tryAcquire方法由子类实现
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 拿到当前锁对象的重入次数
            int c = getState();
            // 如果等于0说明该锁对象没有被任何对象持有
            // 这个时候等待队列可能是有等待节点的,只是恰好锁资源在此刻被释放了
            if (c == 0) {
                // 这里尝试去抢这把锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果此锁被当前对象持有,也就是重入操作,累加state
            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;
        }

        // 尝试释放锁资源
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            // 检查是不是当前线程获得了锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果重入次数等于0了,说明完全释放了这把锁,其他线程可以获取这把锁了
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        protected final boolean isHeldExclusively() {
            // 判是否独占模式
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 创建Condition对象
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        // 重入次数
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        // state=0表示此锁未被占用
        final boolean isLocked() {
            return getState() != 0;
        }
    } 
    
    //

具体实现逻辑已经在源码中注释,简单来说,子类(这里的 Sync)需要实现 AQS 提供的抽象方法,来达到自己个性化的需求。

2. NonfairSync 非公平锁

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        
        final void lock() {
            // 尝试获取锁
            // 这个时候等待队列可能是还有等待节点的,这里取尝试抢一下;
            // 如果锁资源这个时候刚好被释放了,这里是有可能抢成功的
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 既然没有抢成功,那就老老实实取获取锁资源
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    //

公平锁非公平锁的最大区别在于,非公平锁会尽可能的去抢占资源(尽管等待队列存在很多等待节点)。

而公平锁,如果等待队列里存在等待节点,那它是不会去抢占资源的,放进队列,然后按先进先出的顺序去获取资源。

3. FairSync 公平锁实现

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        // 获取锁,如果拿不到会进入阻塞队列中等待
        final void lock() {
            acquire(1);
        }
        
        // 尝试拿取锁,成功则返回true,失败返回false
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 拿到重入次数
            int c = getState();
            // 说明这把锁未被任何线程持有,可以尝试获取锁
            if (c == 0) {
                // 和非公平锁的唯一区别是,这里多了hasQueuedPredecessors判断条件
                // 意思是:首先判断在等待队列里面没有任何等待节点,它才会尝试取获取资源,
                // 否则的话,就不去争抢锁资源了,毕竟是先来先服务嘛(保证公平性)
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) { // CAS设置状态值
                    // 说明获取锁资源成功了,在锁对象中设置exclusiveOwnerThread=当前线程,表明此锁被当前线程锁住了
                    setExclusiveOwnerThread(current); 
                    return true;
                }
            }
            // 判断是否当前线程持有,是的话,就是重入持有,改变state的值
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }    
    //

4. Condition

在特定条件上等待锁资源

来看个例子:

    /**
     * 使用多个Condition实现通知部分线程
     */
    public class ReentrantLockExample {
    
        private Lock lock = new ReentrantLock();
        private Condition conditionA = lock.newCondition();
        private Condition conditionB = lock.newCondition();
    
        public void awaitA() {
            lock.lock();
            try {
                System.out.println("start awaitA at " + System.currentTimeMillis()
                        + " ThreadName:" + Thread.currentThread().getName());
    
                conditionA.await();
    
                System.out.println("end awaitA at " + System.currentTimeMillis()
                        + " ThreadName:" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void awaitB() {
            lock.lock();
            try {
                System.out.println("start awaitB at " + System.currentTimeMillis()
                        + " ThreadName:" + Thread.currentThread().getName());
    
                conditionB.await();
    
                System.out.println("end awaitB at " + System.currentTimeMillis()
                        + " ThreadName:" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void signalAll_A() {
            lock.lock();
            try {
                System.out.println("signalAll_A at " + System.currentTimeMillis()
                        + " ThreadName:" + Thread.currentThread().getName());
    
                conditionA.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
        public void signalAll_B() {
            lock.lock();
            try {
                System.out.println("signalAll_B at " + System.currentTimeMillis()
                        + " ThreadName:" + Thread.currentThread().getName());
    
                conditionB.signalAll();
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            ReentrantLockExample example = new ReentrantLockExample();
            Thread a = new Thread(() -> {
                example.awaitA();
            });
            a.setName("A");
            a.start();
    
            Thread b = new Thread(() -> {
                example.awaitB();
            });
            b.setName("B");
            b.start();
    
            example.signalAll_A();
    
            /**
             * print: 从输出结果可以看出只有线程A被唤醒了
             *
             * start awaitA at 1596380331589 ThreadName:A
             * start awaitB at 1596380331590 ThreadName:B
             * signalAll_A at 1596380331590 ThreadName:main
             * end awaitA at 1596380331590 ThreadName:A
             */
        }
    }
    //

在以上例子中,想要实现的效果是使用多个 Condition 实现通知部分线程,也就是将唤醒粒度变小。

比如说,我现在有 10 个线程,定义了 5 个 condition 对象,每个 condition 上都注册两个线程, 假设某种情况下,10 线程都通过 await 阻塞了,这个时候假如 conditionA 的两个线程可以被唤醒处理其业务了(有可用资源),这个时候我可以做到只唤醒 conditionA 上两个线程,其他线程仍然在阻塞状态;这样就做到了精准唤醒了。

来看看 Condition 的实现原理:

4.1 newCondition

创建 Condition 对象,这里的 ConditionObject 是 AQS 的内部类

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

4.2 await

AbstractQueuedSynchronizer#ConditionObject.await()

也就是我们例子中通过 conditionA.await() 进入阻塞状态,等待其他线程调用 signalAll 或者 signal唤醒

        public final void await() throws InterruptedException {
            // 如果当前线程已经被中断了,就响应中断(也就是抛出异常)
            if (Thread.interrupted())
                throw new InterruptedException();
            // 将当前线程包装成Node放入条件等待队列的队尾   
            Node node = addConditionWaiter();
            // 同时还有释放当前线程已获取的资源
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 如果当前节点不在同步队列里,那就将线程挂起
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 说明当前节点已经从条件队列移到了同步队列中(也就是从await状态被signal唤醒之后,可以尝试获取锁资源进行后续操作了)
            // 从上面被挂起的地方被唤醒之后,尝试去获取锁资源,如果获取失败,那就会进入等待队列中
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            // 记录中断信息    
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
        //

主要操作

  • 将当前线程包装成 node 节点之后放入条件队列
  • 释放当前线程占用的资源
  • 挂起当前线程
  • 当线程被 signal 或者 signalAll 唤醒之后,从条件队列移到同步队列,并尝试获取锁资源

4.2.1 addConditionWaiter

AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter

将当前线程包装成 Node 放入条件等待队列队尾

        /**
         * Adds a new waiter to wait queue.
         * @return its new wait node
         */
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
               // 从条件等待队列中移除取消的节点
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        } 
 //

4.2.2 unlinkCancelledWaiters

AbstractQueuedSynchronizer.ConditionObject#unlinkCancelledWaiters

从条件等待队列中移除被取消的节点

        private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        } 
        //

4.2.3 fullyRelease 释放资源

AbstractQueuedSynchronizer#fullyRelease

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            // 拿到当前线程锁占用的资源,然后释放
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

//

4.2.4 isOnSyncQueue

AbstractQueuedSynchronizer#isOnSyncQueue

判断当前节点是否在同步器队列中,如果是的话说明当前节点正在等待获取资源

    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 注意:在条件等待队列用的nextWaiter来表示下一个节点
        // 因此如果next不为空说明已经在同步队列里面了
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }    
    
    // 从队尾遍历找到这个节点
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }    
    //

4.3 signal 唤醒在条件等待队列中的节点

AbstractQueuedSynchronizer.ConditionObject#signal

        public final void signal() {
            // 必须满足是独占的模式下
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                // 进行唤醒
                doSignal(first);
        } 

4.3.1 doSignal

AbstractQueuedSynchronizer.ConditionObject#doSignal

        // 尝试唤醒条件等待队列中的一个节点,直到唤醒一个为止
        // 遇到取消节点就跳过,如果到队列末端都没有成功,那就结束,说明这个队列没有可唤醒的节点
         private void doSignal(Node first) {
             do {
                 if ( (firstWaiter = first.nextWaiter) == null)
                     lastWaiter = null;
                 first.nextWaiter = null;
             } while (!transferForSignal(first) &&
                      (first = firstWaiter) != null);
         }       

    // AbstractQueuedSynchronizer#transferForSignal
    final boolean transferForSignal(Node node) {
        // 在条件等待队列中有两种状态,要么是CONDITION,要么是CANCELED
        // 如果不是CONDITION,说明是被取消了
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        
        // 从条件等待队列中进入到同步队列
        Node p = enq(node);
        // enq返回的是当前接的前驱节点,如果前驱节点被取消了或者我们设置前驱节点为SIGNAL状态失败了
        // 那我们就自己唤醒当前节点(将前驱节点设置为SIGNAL,最后也是LockSupport.unpark进行唤醒,只不过是在同步队列中等待前驱节点的唤醒而已)
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    //

signalAll 和 signal 原理类似,只不过一个是唤醒所有在当前 condition 上等待的节点,另一个是只唤醒一个,这里不在赘述。




相关文档: