ReentrantLock的上锁、解锁过程分析

本文从源码角度,以ReentrantLock的lock、lockInterruptibly、unlock为入口,对ReentrantLock中公平锁与非公平锁的上锁、解锁、处理中断的上锁过程进行分析。对于内部数据结构的组织形式,这里不进行介绍。由于是多线程并发,所以会有多种不同的情况,源码中针对每个条件,仅给出一种情形,留作参考。

类名简写说明:

  • S:Sync
  • FS:FairSync
  • NFS:NonFairSync
  • RL:ReentrantLock
  • AQS:AbstractQueuedSynchronizer

1. 公平锁

1.1 上锁过程
1.1.1 RL.lock

(1)RL.lock

public void lock() {
     // 调用RL.FS.lock
     sync.lock();
 }

(2)RL.FS.lock

    final void lock() {
        // 调用AQS.acquire
        acquire(1);
    }

(3)AQS.acquire

    public final void acquire(int arg) {
        // 条件1:尝试获取锁(注意前面的"!")
        // 条件2:获取锁失败,调用addWaiter入队、调用acquireQueued再尝试获取锁,失败后当前线程会阻塞
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 到这里说明线程阻塞时被中断,这里重新设置(还原)中断标志位
            // 外部可检测该线程的中断标志位并做出相应处理
            selfInterrupt(); 
    }

(4)RL.FS.tryAcquire

    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取AQS.state的值:1表示锁已经被其他线程持有,0表示没有线程持有锁
        // 注意c是局部变量,后面有很多地方都使用局部变量,而不直接用共享(成员)变量
        int c = getState();
        if (c == 0) {
            // 条件1:判断是否有正在排队的前驱节点(注意前面的"!")
            // 条件2:用CAS的方式将AQS.state设置为acquires(FS中传入的acquires为1)
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                // 将AQS.exclusiveOwnerThread设置为当前线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // AQS.exclusiveOwnerThread就是当前线程,说明当前线程重入了
        else if (current == getExclusiveOwnerThread()) {
            // 到这里不存在并发,因为只有持锁的线程(仅一个) 才能到这里
            // c+1
            int nextc = c + acquires;
            // 溢出判断
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            // 更新AQS.state
            setState(nextc);
            return true;
        }
        // 尝试获取锁失败
        return false;
    }

(5)AQS.hasQueuedPredecessors

    public final boolean hasQueuedPredecessors() {
        // 获取尾节点和头节点
        Node t = tail;
        Node h = head;
        Node s;
        // 条件1:
        //   为false的情况:
        //     情况1:队列未初始化,h和t都为null
        //     情况2:当前锁曾经产生过竞争,且所有线程均已释放锁,此时队列中只剩一个空节点,h和t都指向它
        // 条件2:
        //   条件2.1:
        //     为true的情形:
        //       假设有3个线程thread0、thread1、thread2,初始时,thread0持有锁,thread1执行到AQS.enq的
        //       t.next = node;(还未执行该行),thread2执行到(s=h.next)==null,此时该条件的结果就为true
        //   条件2.2:判断h.next.thread是否为当前线程,若是,则当前线程是调用链是AQS.acquireQueued到
        //            RL.FS.tryAcquire再到该hasQueuedPredecessors方法中
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

(6)AQS.addWaiter

    private Node addWaiter(Node mode) {
        // 创建Node.EXCLUSIVE模式的节点(ReentrantLock中节点是Node.EXCLUSIVE模式的)
        Node node = new Node(Thread.currentThread(), mode);
        // 先尝试快速入队
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // tail为null(即队列尚未初始化)或快速入队失败,在enq中继续处理
        enq(node);
        return node;
    }

(7)AQS.enq

    private Node enq(final Node node) {
        for (;;) {
            // 获取尾节点
            Node t = tail;
            // t为null,说明队列为空
            if (t == null) { 
                // 创建头节点,作为与当前持有锁的线程对应的节点
                // 该节点的thread字段为null
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else { // 将node连接到队列末尾,可能会失败,但在"for(;;)",因此会不断重试,直到成功为止
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t; // 返回node的前驱节点
                }
            }
        }
    }

(8)AQS.acquireQueued

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 获取node的前驱节点
                final Node p = node.predecessor();
                // 若p是头节点,会再次尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    // 将p从队列中删去,将node设置为头节点
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 条件1:
                //   第一次进入该方法时一般返回false,会循环执行到上面tryAcquire处再次尝试获取锁,
                //   第二次进入该方法返回true,会继续判断条件2是否成立
                // 条件2:
                //   true:说明是被中断唤醒
                //   false:说明当前线程是被前面的某个节点释放锁时调用unpark唤醒的
                //     两种情况下,都会循环执行到上面tryAcquire处再次尝试获取锁,
                //     被中断唤醒一般会获取失败,被unpark唤醒则会成功
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    // 将node设置为头节点,并更新字段
    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

(9)AQS.shouldParkAfterFailedAcquire

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 获取前驱节点的waitStatus值:
        //   第一次进入该方法时一般为0,该方法返回false
        //   第二次进入该方法时为-1,该方法返回true
        int ws = pred.waitStatus;
        // ws是否为-1
        if (ws == Node.SIGNAL)
            return true;
        // 调用RL.lockInterruptibly时若park的线程被中断,可能会出现ws大于的情况(后面介绍RL.lockInterruptibly)
        // ws为1的情形:
        //   假设有三个线程thread0、thread1、thread2,初始时,thread0持有锁thread1和thread2都加入阻塞队列后被
        //   park,之后外部中断thread1,thread1会执行到AQS.cancelAcquire的node.waitStatus = Node.CANCELLED;处将
        //   s.waitStatus设置1,之后tread1会执行到AQS.cancelAcquire的unparkSuccessor(node);处将thread2唤醒,无论
        //   此时thread0是否释放锁,thread2会循环执行到这里,”清理“掉处于Node.CANCELLED状态的thread1对应的节点,
        //   处理完后会返回false,继续循环,此后若thread0仍未释放锁,thread2会继续park,等待thread0释放锁时唤醒它
        if (ws > 0) { 
            // 删除node之前所有Node.CANCELLED状态的节点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else { // ws为0
            // 以CAS方式将pred.waitStatus设置为-1
            // 注意设置的是前驱节点的waitStatus,前驱节点释放锁时,会根据该字段是
            // 否为-1来“决定”是否有要被唤醒的后继节点,-1表示有后继节点需要被唤醒
            // (因为前驱节点并不知道是否有后继,所以当有后继节点时,
            //   后继要“负责”将前驱的waitStatus字段设置为-1)
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

(10)AQS.parkAndCheckInterrupt

    private final boolean parkAndCheckInterrupt() {
        // 挂起当前线程
        // 注意当外部设置了当前线程中断标志位时,当前线程会被唤醒
        // 只要中断标志不被清除,当前线程下次调用park也不会被阻塞
        LockSupport.park(this);
        // 若当前线程的中断标志位被设置了,则返回true,否则返回false
        // 注意调用Thread.interrupted后会将中断标志清除
        return Thread.interrupted();
    }
1.1.2 RL.lockInterruptibly

(1)RL.lockInterruptibly

    public void lockInterruptibly() throws InterruptedException {
        // 调用AQS.acquireInterruptibly
        sync.acquireInterruptibly(1);
    }

(2)AQS.acquireInterruptibly

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        // 先判断当前线程是否被中断,被中断则抛出异常        
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取锁(见上面1.1.1(4)处)(注意前面的"!")
        if (!tryAcquire(arg))
            // doAcquireInterruptibly与上面1.1.1(8)处的acquireQueued类似
            // 主要区别是对中断进行了处理
            doAcquireInterruptibly(arg);
    }

(3)AQS.doAcquireInterruptibly

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        // 入队(见上面1.1.1(6))
        final Node node = addWaiter(Node.EXCLUSIVE);
        // 下面的逻辑与acquireQueued中的逻辑几乎一致
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    // 不同点在这里,若当前线程被中断,这里直接抛出异常
                    // 不会再尝试获取锁
                    throw new InterruptedException();
            }
        } finally {
            // 抛出异常时failed为true
            if (failed)
                cancelAcquire(node);
        }
    }

(4)AQS.cancelAcquire

    private void cancelAcquire(Node node) {
        // 忽略不存在的node
        if (node == null)
            return;
        
        node.thread = null;

        // node之前可能存在多个Node.CANCELLED(CANCELLED为1)状态的节点
        // 这里向前变量获取不为Node.CANCELLED的节点,并设置为node的前驱
        Node pred = node.prev;
        // 条件为true的情形:
        //   假设有4个线程thread0、thread1、thread2、thread3,初始时,thread0持有锁,其他三个
        //   线程按顺序加入队列且均被park,之后先中断唤醒thread2,thread2执行完AQS.cancelAcquire
        //   中的node.waitStatus = Node.CANCELLED;处后,再中断thread3,thread3到这里条件就会成立
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // predNext可能是pred和node之间的状态为Node.CANCELLED的节点,也可能就是node
        Node predNext = pred.next;
        // 将node.waitStatus设置为Node.CANCELLED状态(CANCELLED为1)
        node.waitStatus = Node.CANCELLED;

        // 情况1:node是tail
        //   假设有两个线程thread0、thread1,初始时,thread0持有锁thread1和
        //   加入阻塞队列后被park,此时外部中断thread1,thread1会执行到这里
        // 将pred设置为tail(即断开node)
        if (node == tail && compareAndSetTail(node, pred)) {
            // 将pred.next设置为null
            compareAndSetNext(pred, predNext, null);
        } else { // node不是tail或CAS失败
            // 对于下面两种情况,由于没有将node的后继指向pred,仍指向node,因此在node
            // 的后继节点被唤醒后,AQS.shouldParkAfterFailedAcquire中的条件ws > 0会成立
            int ws;
            // 情况2:pred不是head
            // 条件2:
            //   条件2.1为true的情形:
            //     假设有四个线程thread0、thread1、thread2、thread3,初始时,thread0
            //     持有锁,后三个线程依次加入队列后被park,此时外部中断thread2,thread2
            //     会执行到这里,且此时条件1和条件2.1均为true
            //   条件2.2为true的情形:尚不明确
            //   条件3:pred.thread为null,说明在条件1和条件3之间pred被唤醒,成为头节点,此时
            //          情况2就变成了情况3,pred.thread != null为false,跳转到情况3处继续执行
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    // 以CAS方式将next设置为pred的后继节点,后面pred会直接唤醒next,而不是node
                    compareAndSetNext(pred, predNext, next);
            } else { 
                // 情况3:pred是head
                //   假设有三个线程thread0、thread1、thread2,初始时,thread0持有锁,thread1
                //   和thread2都加入队列后被park,此时外部中断thread1,thread1会执行到这里
                // pred是head,直接唤醒node的后继节点,若此时仍未释放锁,
                // 后继节点对应的线程被唤醒后最终会再次被park
                unparkSuccessor(node);
            }
                
            node.next = node; // help GC
        }
    }

(5)AQS.unparkSuccessor

    private void unparkSuccessor(Node node) {
       
        // 情况1:从AQS.cancelAcquire方法调用unparkSuccessor到这里,此时node.waitStatus为1
        // 情况2:从AQS.release方法调用unparkSuccessor到这里,此时node.waitStatus为-1
        int ws = node.waitStatus;
        // ws为-1,将node.waitStatus设置为0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        // 条件1:
        //   为true的情形:
        //     假设只有两个线程thread0、thread1,初始时,thread0持有锁,处于挂起状态
        //     的thread1被外部中断唤醒,则thread1会执行到上面AQS.cancelAcquire的情况1处
        //     被清除出队列,之后thread0释放锁,它会执行到这里,此时s就为null
        // 条件2:
        //   为true的情形:
        //     假设有三个线程thread0、thread1、thread2,初始时,thread0持有锁thread1和
        //     thread2都加入阻塞队列后被park,之后外部中断thread1,thread1会执行到
        //     AQS.cancelAcquire的node.waitStatus = Node.CANCELLED;处将s.waitStatus设置
        //     1,之后thread0释放锁会执行到这里,此时s.waitStatus就为1,条件成立
        if (s == null || s.waitStatus > 0) {
            // 假设到这里是因为s.waitStatus > 0成立,若for循环中未找到合适的节点,
            // 这种情况下不将s设置为null,下面if (s != null)会成立,不符合”要求“
            s = null;
            // 从tail向前遍历查找最近一个要被唤醒的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                // t.waitStatus为0说明t是最后一个节点
                // t.waitStatus为-1说明是最后一个之前的正常的节点
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            // 唤醒s对应的线程
            // 注意多次unpark只能唤醒一次park 
            LockSupport.unpark(s.thread);
    }
1.2 解锁过程

(1)RL.unlock

    public void unlock() {
        // 调用AQS.release
        sync.release(1);
    }

(2)调用AQS.release

    public final boolean release(int arg) {
        // 尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;
            // h不为null说明存在竞争,队列已经被初始化
            // h.waitStatus不为0(即为-1),说明有要被唤醒的后继节点
            if (h != null && h.waitStatus != 0)
                // 唤醒后继节点(见上面1.1.2(5)处)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

(3)RL.S.tryRelease

    protected final boolean tryRelease(int releases) {
        // ReentrantLock是可重入的,因此c不一定为0
        int c = getState() - releases;
        // 该条件可防止外部未持有锁的线程调用RL.unlock或持有锁的线程RL.lock次数大于RL.unlock次数
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // c为0说明是最后一次调用RL.unkock
        if (c == 0) {
            free = true;
            // 将独占线程设置为null
            setExclusiveOwnerThread(null);
        }
        // 更新AQS.state
        setState(c);
        // 返回true,表示是最后一个RL.unkock
        // 返回false,表示当前线程重入锁了,且不是最后一个RL.unkock
        return free;
    }

2. 非公平锁

2.1 上锁过程

(1)RL.lock

    public void lock() {
        // 调用RL.NFS.lock
        sync.lock();
    }

(2)RL.NFS.lock

    final void lock() {
        // 先竞争锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 与公平锁一样,调用AQS.acquire
            acquire(1);
    }

(3)AQS.acquire

    public final void acquire(int arg) {
        // 条件1中调用的是RL.NFS.tryAcquire,其他与公平锁一样
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

(4)RL.NFS.tryAcquire

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

(5)RL.S.nonfairTryAcquire

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

非公平锁的入队、阻塞、中断、解锁等过程与公平锁中的一致,不再介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容