ReentrantLock之Condition源码解读

1.背景

阅读该源码的前提是,已经阅读了reentrantLock的源码!

2.await源码解读

condition代码理解的核心,其实就是理解到:

线程节点如何从sync双向链表队列到指定的条件队列中,

然后又如何从条件队列中到sync双向链表队列的

一定要先把下面的2个图理解到,再去看源码和断点调试就很容易理解了

核心逻辑图:

 核心代码逻辑图:

 

2.1.await方法详解

代码解读:

  /**
     * 进入条件等待
     */
    public final void await() throws InterruptedException {
        // 是否有中断标记
        if (Thread.interrupted())
            throw new InterruptedException();
        // 将线程加入到条件等待队列
        Node node = addConditionWaiter();
       /* fullyRelease(node) 释放锁并唤醒后继节点
         这里要结合ReentrantLock来理解,执行到这里说明是获取到了锁的,
         这里就是要释放ReentrantLock锁,然后进入到条件队列中等待*/
        int savedState = fullyRelease(node);
        // interruptMode =0表示没有中断, interruptMode =1表示退出时重新中断 ,interruptMode=-1表示退出时抛异常
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
            // 在条件队列中等待
            LockSupport.park(this);
            // checkInterruptWhileWaiting(node)返回0,表示没有中断
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        // acquireQueued(node, savedState) 从sync队列中重新获取锁,并处理中断标记
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        // node结点不是最后一个结点,清除条件队列中无效的节点
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
        // 重新处理中断,具体中断方式取决于 interruptMode 的值,
        // interruptMode =1表示退出时重新中断 ,interruptMode=-1表示退出时抛异常int interruptMode = 0;
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

 

2.2.addConditionWaiter方法详解

代码解读:

  /**
     * 添加新的节点到条件队列
     * 这里的条件队列是 单链表,不是双链表
     * CONDITION = -2 表示是条件队列
     */
    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // t.waitStatus != Node.CONDITION ?? 这个条件的作用
        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;
    }

 

2.3.fullyRelease方法详解

代码解读:

 /**
     * 释放锁,并唤醒后继节点
     *
     * @param node
     * @return
     */
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            // 获取当前资源状态
            int savedState = getState();
            // release(savedState) 释放锁并唤醒后继节点
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            // 释放锁失败节点取消
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

 

2.4.isOnSyncQueue方法详解

代码解读:

 /**
     * 判定节点是否在sync队列中
     *
     * @param node
     * @return
     */
    final boolean isOnSyncQueue(Node node) {
        // 如果节点标记位是CONDITION = -2的状态 或者 没有前继节点,说明节点不在队列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 如果下一个节点不为空说明节点在队列里面
        if (node.next != null) // If has successor, it must be on queue
            return true;
        // 遍历sync队列,查看节点是否在队列里面
        return findNodeFromTail(node);
    }

 

2.5.findNodeFromTail方法详解

代码解读:

   /**
     * 遍历节点,查看传入的节点是否在队列里面,在里面返回true
     *
     * @param node
     * @return
     */
    private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (; ; ) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            // 从后往前遍历,还记得之前我们在讲ReentrantLock的源码时说过为什么要从后往前遍历么?
            t = t.prev;
        }
    }

 

2.6.checkInterruptWhileWaiting方法详解

代码解读:

 /**
     * // 该模式意味着在退出等待时重新中断
     * private static final int REINTERRUPT = 1;
     * // 该模式意味着在退出等待时抛出InterruptedException
     * private static final int THROW_IE = -1;
     *
     * @param node
     * @return
     */
    private int checkInterruptWhileWaiting(Node node) {
        // 当前线程没有被中断直接返回0
        // 当前线程已经被中断了的话
        return Thread.interrupted() ?
                // 取消时重新入队列成功,标记为退出时抛出异常
                // 取消时重新入队列失败,标记位退出时重新中断
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
    }

 

2.7.transferAfterCancelledWait方法详解

代码解读:

 /**
     * 条件等待队列中的节点如果已经被取消,将节点添加到sync队列的尾部
     *
     * @param node
     * @return 节点添加到尾部成功返回true, 否则返回false
     */
    final boolean transferAfterCancelledWait(Node node) {
        // 初始化节点
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            // 将节点添加到尾部
            enq(node);
            // 添加到队列成功返回true
            return true;
        }
        // 判断节点是否在Sync队列里面
        while (!isOnSyncQueue(node))
            // 不在队列里面则等待,直到线程执行完成
            Thread.yield();
        // 添加失败,返回false
        return false;
    }

 

2.8.unlinkCancelledWaiters方法详解

代码解读:

 /**
     * 作用:删除单项向链表中已经取消的节点,即状态不等于2的节点
     * 这是典型单向链表删除节点的逻辑,如果对这个段代码不是很理解,
     * 可以查看之前的数据结果部分
     */
    private void unlinkCancelledWaiters() {
        Node t = firstWaiter;
        Node trail = null;
        while (t != null) {
            Node next = t.nextWaiter;
            if (t.waitStatus != Node.CONDITION) {
                // 断开连接,帮助GC回收
                t.nextWaiter = null;
                if (trail == null)
                    // 重新定义头结点
                    firstWaiter = next;
                else
                    trail.nextWaiter = next;
                if (next == null)
                    // 最后的有效尾节点
                    lastWaiter = trail;
            } else {
                trail = t;
            }
            // 指针后移
            t = next;
        }
    }

 

2.9.reportInterruptAfterWait方法详解

代码解读:

 /**
     * 中断的具体处理方式
     * interruptMode =1表示退出时重新中断 ,interruptMode=-1表示退出时抛异常
     *
     * @param interruptMode
     * @throws InterruptedException
     */
    private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
        if (interruptMode == THROW_IE)
            // 抛出异常的处理方式
            throw new InterruptedException();
        else if (interruptMode == REINTERRUPT)
            // 自我中断的处理方式
            selfInterrupt();
    }

 

2.10.selfInterrupt方法详解

代码解读:

 /**
     * 当前线程执行中断处理
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

 

 3.signal源码解读

3.1.signal源码解读

代码:

    /**
     * 唤醒条件队列中的节点
     */
    public final void signal() {
        // 检查当前线程是否是拥有锁的线程
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            // 执行唤醒方法
            doSignal(first);
    }

 


3.2.isHeldExclusively源码解读

代码:

    /**
     * 判定当前线程是否是拥有锁的线程
     * @return
     */
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

 


3.3.doSignal源码解读

代码:

    /**
     * 执行唤醒条件队列中的节点
     * @param first
     */
    private void doSignal(Node first) {
        do {
            // 这个if的判定就是检查条件队列中是否还有节点,如果没有了,就将lastWaiter设置为null
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            // 第一个节点出队列后,断开引用
            first.nextWaiter = null;
        } while (!transferForSignal(first) && (first = firstWaiter) != null);
    }

 


3.4.transferForSignal源码解读

 /**
     * 唤醒条件队列中的节点--> 到 sync对列中去
     * @param node
     * @return
     */
    final boolean transferForSignal(Node node) {
        // 修改状态为 sync队列的初始化状态
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 将节点加入到队列尾部
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            // 换醒节点
            LockSupport.unpark(node.thread);
        return true;
    }

 

4.测试

不论你是否理解了源码,都建议大家多使用断点调试查看

节点是如何进入队列,

如何出队列,

如何挂起线程,

如何唤醒线程的.....

测试代码

package com.my.aqs.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {
    // 功能标记位
    private int flag = 1;
    // 创建非公平锁
    private Lock lock = new ReentrantLock(false);
    // 条件锁1-烧水
    private Condition condition1 = lock.newCondition();
    // 条件锁2-泡茶
    private Condition condition2 = lock.newCondition();
    // 条件锁3-喝茶
    private Condition condition3 = lock.newCondition();

    /**
     * 功能:烧水
     */
    public void method01() {
        String threadName = Thread.currentThread().getName();
        try {
            lock.lock();
            while (flag != 1) {
                System.out.println("           "+threadName + ",需要,挂起当前线程,进入条件等待队列,因为当前不是烧水标记1,而是:" + flag);
                condition1.await();
            }
            System.out.println(threadName + ":正在烧水...");
           // System.out.println(threadName + ":烧水完成,唤醒泡茶线程");
            flag = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 功能:泡茶
     */
    public void method02() {
        String threadName = Thread.currentThread().getName();
        try {
            lock.lock();
            while (flag != 2) {
                System.out.println("           "+threadName + ",需要,挂起当前线程,进入条件等待队列,因为当前不是泡茶标记2,而是:" + flag);
                condition2.await();
            }
            System.out.println(threadName + ":正在泡茶...");
           // System.out.println(threadName + ":泡茶完成,唤醒喝茶线程");
            flag = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 功能:喝茶
     */
    public void method03() {
        String threadName = Thread.currentThread().getName();
        try {
            lock.lock();
            while (flag != 3) {
                System.out.println("           "+threadName + ",需要,挂起当前线程,进入条件等待队列,因为当前不是喝茶标记3,而是:" + flag);
                condition3.await();
            }
            System.out.println(threadName + ":正在喝茶...");
           // System.out.println(threadName + ":喝茶完成,唤醒烧水线程");
            flag = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 睡眠时间是为了让线程按照这个顺序进入队列等待 喝茶->泡茶->烧水
     * @param args
     */
    public static void main(String[] args) {
        ConditionTest conditionTest = new ConditionTest();
        for (int i = 1; i <= 2; i++) {
            // 烧水
            new Thread(() -> {
                try {
                    Thread.sleep(5*1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                conditionTest.method01();
            }, "烧水-线程 " + i).start();
            // 泡茶
            new Thread(() -> {
                try {
                    Thread.sleep(5*100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                conditionTest.method02();
            }, "泡茶-线程 " + i).start();
            // 喝茶
            new Thread(() -> {
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                conditionTest.method03();
            }, "喝茶-线程 " + i).start();
        }
    }
}
View Code

 断点调试图:

 测试结果:

完美

posted @ 2023-09-26 15:35  李东平|一线码农  阅读(18)  评论(0编辑  收藏  举报