小记
好久没更博,窗外光芒万丈,冬日的晚晨,多么美好,就不浪费了,循着键盘上的点点星辰,开工!

啥子是条件队列?

我们都知道,在万类之祖Object里面定义了几个监视器方法:wait(),notify
(),notifyAll(),配合synchronized语义来控制线程的一些状态,在JDK1.5之后,由Lock替代了synchronized,而这几个监视器由条件队列Condition来实现,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”),以原子的方式释放锁,并挂起当前线程,所以,也可以叫它为线程的条件队列(自创的)。

来看一个应用示例

在以前刚学习Java的时候写过一个题,题干大概是这样的:开启三个线程依次轮流打印出75个数,且次序不能乱。下面是代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class CountTo_75 {public static void main(String[] args) {final Box box = new Box();new Thread(new Runnable() { // 线程1@Overridepublic void run() {for (int i = 1; i <= 5; i++) {box.main_1();}}},"Thread-1").start();new Thread(new Runnable() { // 线程2@Overridepublic void run() {for (int i = 1; i <= 5; i++) {box.main_2();}}},"Thread-2").start();new Thread(new Runnable() { // 线程3@Overridepublic void run() {for (int i = 1; i <= 5; i++) {box.main_3();}}},"Thread-3").start();}static class Box {Lock lock = new ReentrantLock();Condition condition_1 = lock.newCondition();Condition condition_2 = lock.newCondition();Condition condition_3 = lock.newCondition();private volatile int flag = 1;public static int count = 0;   //用于计数的变量public void main_1() {lock.lock();try {while(flag != 1){try {condition_1.await();} catch (InterruptedException e) {e.printStackTrace();}}for (int j = 0; j < 5; j++) {count++;System.out.println(Thread.currentThread().getName() +" "+ count);}flag = 2;condition_2.signal();} finally {lock.unlock();}}public void main_2() {lock.lock();try {while(flag != 2){try {condition_2.await();} catch (InterruptedException e) {e.printStackTrace();}}for (int j = 0; j < 5; j++) {count++;System.out.println(Thread.currentThread().getName() +" " + count);}flag = 3;condition_3.signal();} finally {lock.unlock();}}public void main_3() {lock.lock();try {while(flag != 3){try {condition_3.await();} catch (InterruptedException e) {e.printStackTrace();}}for (int j = 0; j < 5; j++) {count++;System.out.println(Thread.currentThread().getName() + " " + count);}flag = 1;condition_1.signal();} finally {lock.unlock();}}}
}

抛开当时混乱的逻辑和性能考虑不足不谈,这段丑陋的代码,终归是实现了功能,而且是运用了Lock和Condition来实现的,用在这里来说明Condition的语义再好不过了。代码中初始化了3个条件队列,分别来控制3个线程的挂起状态,flag变量则控制它们之间的关系。

与Lock之间的实现关系

Condition是一个接口,其实现类只有两个:AQS和AQLS,都以内部类的形式存在,内部类叫做ConditionObject,这里有点纳闷,既然这个类是为Lock专属定制的,为什么不在ReentrantLock里面来实现呢?放在AQS不会太臃肿吗?不知道Doug Lea上神当时是怎么考虑的。
由于在AQS中已经实现,因此在ReentrantLock里面对其操作也是很简单的,创建一个条件队列:

    public Condition newCondition() {return sync.newCondition();}

sync中的实现:

    abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;final ConditionObject newCondition() {return new ConditionObject();}}

很简单吧?呵呵,下面来看ConditionObject

ConditionObject实现

首先定义了两个核心成员变量,条件队列的头节点和尾节点:

/** First node of condition queue. */
private transient Node firstWaiter;/** Last node of condition queue. */
private transient Node lastWaiter;
1、核心方法:await() 不可中断的条件等待实现
public final void await() throws InterruptedException {//当前线程被中断则抛异常if (Thread.interrupted()) throw new InterruptedException();//添加进条件队列Node node = addConditionWaiter();//释放当前线程持有的锁int savedState = fullyRelease(node);int interruptMode = 0;//在这里一直查找创建的Node节点在不在Sync队列,不在就一直禁用当前线程while (!isOnSyncQueue(node)) {//park当前线程,直到唤醒LockSupport.park(this);//如被中断也跳出循环if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break;}//此时已被唤醒,获取之前被释放的锁,if (acquireQueued(node, savedState) && interruptMode != THROW_IE)   interruptMode = REINTERRUPT;//移除节点,释放内存if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();//被中断后的操作if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

简而言之,await()方法其实就是为当前线程创建一个Node节点,加入到Condition队列并释放锁,之后就一直查看这个节点是否在Sync队列中了(signal()方法将它移到Sync队列),如果在的话就唤醒此线程,重新获取锁。此外,awaitNanos(long nanosTimeout) 方法和await(long time, TimeUnit unit) 方法的实现大同小异,只是在何时跳出while循环的时候加了一个超时罢了。
另外还有几个相关的方法也看一下:

/***  添加一个Node节点到Condition队列中*/
private Node addConditionWaiter() {Node t = lastWaiter;//如果尾节点被取消,就清理掉if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;}//新建状态为的CONDITION节点,并添加在尾部Node node = new Node(Thread.currentThread(), Node.CONDITION);if (t == null)firstWaiter = node;elset.nextWaiter = node;lastWaiter = node;return node;
}
    /***  释放当前线程的state,实际还是调用tryRelease方法*/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;}}
    /***  检查当前节点在不在Sync队列*/final boolean isOnSyncQueue(Node node) {//如果当前节点状态为CONDITION,一定还在Condition队列//如果Sync队列的前置节点为null,则表明当前节点一定还在Condition队列if (node.waitStatus == Node.CONDITION || node.prev == null)return false;//有后继节点,也有前置节点,那么一定在Sync队列if (node.next != null) // If has successor, it must be on queuereturn true;//倒查Node节点,前置节点不能为null,第一个if已经做了判断,其前置节点为non-null,但是当前节点也不在Sync也是可能的,因为CAS操作将其加入队列也可能失败,所以我们需要从尾部开始遍历确保其在队列return findNodeFromTail(node);}
2、核心方法:signal() 将等待时间最长的线程(如果存在)从Condition队列中移动到拥有锁的Sync队列
   public final void signal() {//当前线程非独占线程,报非法监视器状态异常if (!isHeldExclusively())throw new IllegalMonitorStateException();//头节点是等待时间最长的节点Node first = firstWaiter;if (first != null)doSignal(first);}
   private void doSignal(Node first) {do {//如果头节点的下一节点为null,则将Condition的lastWaiter置为nullif ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null;//将头结点的下一个节点设为nullfirst.nextWaiter = null;//被唤醒并且头节点不为null则结束循环} while (!transferForSignal(first) &&(first = firstWaiter) != null);}
   final boolean transferForSignal(Node node) {//如果无法改变节点状态,说明节点已经被唤醒if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))return false;//将当前节点添加到Sync队列尾部,并设置前置节点的waitStatus为SIGNAL,表明后继有节点(可能)将被唤醒,如果取消或者设置waitStatus失败,会唤醒重新同步操作,这时候waitStatus是瞬时的,出现错误也是无妨的Node p = enq(node);int ws = p.waitStatus;if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))LockSupport.unpark(node.thread);return true;}
3、核心方法:signalAll() 将所有线程从Condition队列移动到拥有锁的Sync队列中。
   public final void signalAll() {if (!isHeldExclusively())throw new IllegalMonitorStateException();Node first = firstWaiter;if (first != null)doSignalAll(first);}
    /*** 遍历所有节点,加入到拥有锁的Sync队列*/private void doSignalAll(Node first) {lastWaiter = firstWaiter = null;do {Node next = first.nextWaiter;first.nextWaiter = null;transferForSignal(first);first = next;} while (first != null);}

线程条件队列ConditionObject源码解读相关推荐

  1. 并发编程之 Executor 线程池原理与源码解读

    并发编程之 Executor 线程池原理与源码解读 线程是调度 CPU 资源的最小单位,线程模型分为 KLT 模型与 ULT 模型,JVM使用的是 KLT 模型.java线程与 OS 线程保持 1:1 ...

  2. 并发编程之Executor线程池原理与源码解读

    1. 线程池 "线程池",顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不 仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配. 调 ...

  3. hystrix 源码 线程池隔离_“池”的思想:从java线程池到数据库连接池的源码解读(1)...

    一. java线程池 带着问题: 线程是什么时候被创建的? 线程会一直循环取任务任务吗?怎么做的? 线程取不到任务会怎么样? 线程会被Runnable和Callable的异常干掉吗? 线程怎么干掉自己 ...

  4. 深入Java线程池:从设计思想到源码解读

    点击关注公众号,实用技术文章及时了解 初识线程池 我们知道,线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的.出于避免频繁创建.销毁线程以及方便线程管理的需要,线程池应运而生. 线程池优势 ...

  5. java并发编程——线程池的工作原理与源码解读

    2019独角兽企业重金招聘Python工程师标准>>> 线程池的简单介绍 基于多核CPU的发展,使得多线程开发日趋流行.然而线程的创建和销毁,都涉及到系统调用,比较消耗系统资源,所以 ...

  6. Executors源码解读——创建ExecutorService线程池

    Executors源码解读--创建ExecutorService线程池 〇.[源码版本] jdk 1.8 一.线程池概述 二.线程池创建 三.Executors源码解读 newFixedThreadP ...

  7. Cond:条件变量源码解读

    Cond条件变量 针对场景 应用于一组goroutine等待某一个条件,当条件为true时唤醒某一个或者所有的goroutine 源码解读 type Cond struct {noCopy noCop ...

  8. 【Java 并发编程】线程池机制 ( 线程池执行任务细节分析 | 线程池执行 execute 源码分析 | 先创建核心线程 | 再放入阻塞队列 | 最后创建非核心线程 )

    文章目录 一.线程池执行任务细节分析 二.线程池执行 execute 源码分析 一.线程池执行任务细节分析 线程池执行细节分析 : 核心线程数 101010 , 最大小成熟 202020 , 非核心线 ...

  9. aqs java 简书,Java AQS源码解读

    1.先聊点别的 说实话,关于AQS的设计理念.实现.使用,我有打算写过一篇技术文章,但是在写完初稿后,发现掌握的还是模模糊糊的,模棱两可. 痛定思痛,脚踏实地重新再来一遍.这次以 Java 8源码为基 ...

最新文章

  1. 【错误总结】LaTex Warning: citation undefined
  2. c++ volatile关键字
  3. ORA-01940,删除某用户的所有对象
  4. 设置ubuntu默认python3设置
  5. python 矩阵类型转换_Python3 列表,数组,矩阵的相互转换的方法示例
  6. JAVA基础-XML的解析
  7. Visual Studio 201~ Code 格式检查
  8. Atitit sql的执行功能 目录 1. 主要流程 1 1.1. 获取conn,执行sql取得结果, 1 1.2. Orm类的执行(hb mybatis为例 1 2. 常见sql执行框架与类库 1
  9. 《深入解析Windows操作系统第4版》随笔记录03
  10. vtk 实现mimics软件中的Split/Merge算法
  11. vue3 动态获取屏幕尺寸
  12. JavaScript大师必须掌握的12个知识点 1
  13. Rockchip开发系列 - 3.Pin-Ctrl 开发指南
  14. iOS和Android的APP启动图标和应用商店截图尺寸
  15. 艾美网帮助您实现肌肤有效美白
  16. 站长问答:百度突然不收录了怎么办?
  17. 5套独立的app手机端模板界面代码
  18. linux下登陆FTP
  19. 正则表达式验证时间格式
  20. 3.致远OA二次开发Rest用户的介绍

热门文章

  1. 读入数据+使用snownlp进行情感分析
  2. Host管理工具 SwitchHosts
  3. idea中好用的git shelve changes和stash changes
  4. 张三学前端-Promise篇
  5. 模取幂运算 计算a^b mod n
  6. Spring Security进行登录认证和授权
  7. UCC27201DDAR
  8. linux mate中文输入法,树莓派3b基于UbuntuMate下载中文输入法(示例代码)
  9. postGresql关键字字段重名
  10. 全网最全软件测试工程师面试题,看完你还怕拿不到offer?