关于java线程浅谈五: Condition条件
来源:互联网 发布:macbook藏文件软件 编辑:程序博客网 时间:2024/05/18 11:48
关于java线程浅谈五: Condition条件
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
- 什么是Condition条件
- Condition 的使用
- Condition 的实现类ConditionObject
- Condition 与 AQS 到底是怎么工作的
Java.util.concurrent 包在java语言中可以说是比较难啃的一块,但理解好这个包下的知识,对学习java来说,不可谓是一种大的提升,我也尝试着用自己不聪明的脑袋努力的慢慢啃下点东西来。其实 java.util.concurrent 包中,最核心的就是AQS( AbstractQueuedSynchronizer) 这个抽象类,可以说是整个JUC包的基石,但今天先不说AQS,我先从比较容易理解的 Condition 条件讲起。
什么是Condition条件
Condition 是定义在 java.util.concurrent.locks 包下的一个接口,这个接口主要的功能就是实现了与Object类中的wait(),notify()方法相同的语义,但是功能上更强大。Condition 接口中定义的方式其实很少,列举下来:
//使当前线程接到signal信号之前,或者被中断之前一直处于等待状态 void await() throws InterruptedException; //使当前线程接到signal信号之前一直处于等待状态 void awaitUninterruptibly(); //使当前线程接到signal信号之前,或到达指定等待的时间之前,或者被中断之前一直处于等待状态 boolean await(long time, TimeUnit unit) throws InterruptedException; //使当前线程接到signal信号之前,或到达指定的最后期限时间之前,或者被中断之前一直处于等待状态 boolean awaitUntil(Date deadline) throws InterruptedException; //使当前线程接到signal信号之前,或到达指定等待的时间之前,或者被中断之前一直处于等待状态 long awaitNanos(long nanosTimeout) throws InterruptedException; //向一个线程发送唤醒信号 void signal(); //向所有线程发送唤醒信号 void signalAll();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
从定义的方法中也可以看出这个类的功能,无非就两种:等待方法、唤醒等待方法。
Condition 的使用
下面用一个之前用的小demo展示一下 Condition 的使用方法:
先使用Object中的wait,notify 方法:
public class TestCondition { public static void main(String[] args) { try { ThreadTest t1 = new ThreadTest("t1"); synchronized (t1) { System.out.println(Thread.currentThread().getName()+"线程启动线程 t1"); t1.start(); System.out.println(Thread.currentThread().getName()+"线程执行wait方法,等待被唤醒"); t1.wait(); System.out.println(Thread.currentThread().getName()+"线程继续执行"); } } catch (InterruptedException e) e.printStackTrace(); } }}class ThreadTest extends Thread{ public ThreadTest(String name){ super(name); } public void run(){ synchronized (this) { System.out.println(Thread.currentThread().getName()+"线程执行 notify 方法"); notify(); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
运行结果:
main线程启动线程 t1main线程执行wait方法,等待被唤醒t1线程执行 notify 方法main线程继续执行
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
这里就不再详述关于 synchronized 关键字的使用,举这个示例是为了引出Condition的使用。
稍微改一下上面的程序:
public class TestCondition { private static ReentrantLock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); public static void main(String[] args) { try { ThreadTest t1 = new ThreadTest("t1"); lock.lock(); System.out.println(Thread.currentThread().getName()+"线程启动线程 t1"); t1.start(); System.out.println(Thread.currentThread().getName()+"线程执行condition.await()方法,等待被唤醒"); condition.await(); System.out.println(Thread.currentThread().getName()+"线程继续执行"); } catch (InterruptedException e) { e.printStackTrace(); }finally{ lock.unlock(); } } static class ThreadTest extends Thread{ public ThreadTest(String name){ super(name); } public void run(){ try{ lock.lock(); System.out.println(Thread.currentThread().getName()+"线程执行condition.signal()方法"); condition.signal(); }finally{ lock.unlock(); } } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
运行结果:
main线程启动线程 t1main线程执行condition.await()方法,等待被唤醒t1线程执行condition.signal()方法main线程继续执行
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
根据上面的实例可以看出,Condition工具类是配合Lock类一起使用的,当然Condition的作用远不止上面代码这样简单,其实它最主要的作用是可以在同一把锁上,针对不同的业务使用不用的Condition。比如在前面的文章提到的生产消费问题,我们完全可以使用两个Condition,一个针对于生产,一个针对于消费,当产品为0时,我们可以让消费的Condition执行await方法,当产品不为0时,可以让消费的Condition执行signal方法。生产者也是类似,具体代码这里就不再详述了,可以尝试自己实现一下。
Condition 的实现类:ConditionObject
Condition 是一个接口,那它到底是怎么工作的呢?我们来看一下ReentrantLock 类是怎么样使用Condition 的。
Condition condition = lock.newCondition();//这是生成Condition 的方法
- 1
- 1
追踪一下newCondition( )这个方法,我们就可以看到一个Condition 的具体实现:
public Condition newCondition() { return sync.newCondition(); //调用sync的newCondition }//sync的方法,返回一个new ConditionObject()final ConditionObject newCondition() { return new ConditionObject(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这个sync是什么呢?它是定义在ReentrantLock 中的内部类,它继承了上面提到的AQS这个抽象类,从某种角度来说ReentrantLock 只是提供了一个可以操作AQS这个核心类的入口,代理了一些重要的方法,那为什么不让ReentrantLock 直接继承AQS,而是选择用一个内部类来实现呢,可能是出于一些安全性方面的考虑。ConditionObject是定义在AQS中的。
Condition 与 AQS 到底是怎么工作的?
探究这个问题,就需要来看一下ConditionObject的源码了。
代表性的,我们看一下await方法和signal方法的源码(基于jdk1.8.0_112):
await方法:
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //判断线程的中断状态,如果中断则抛出异常 //将当前线程包装成一个条件等待节点添加到ConditionObject维护的一个队列中 Node node = addConditionWaiter(); //释放当前线程占有的锁 int savedState = fullyRelease(node); int interruptMode = 0; //这个while 循环就是在当前线程释放锁后,一直观察持有自己线程的节点有没有被加载到 //AQS维护的等待队列中(加入到这个队列中才有获取锁的资格),什么时候会加入到这个队列中呢? //当然是执行了唤醒这个线程对应的singal方法的时候啦 while (!isOnSyncQueue(node)) { 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 cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在看一下signal方法:
public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter;//ConditionObject维护队列中的头节点,进行唤醒操作 if (first != null) doSignal(first); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
从上面的叙述不难看出,AQS 和 ConditionObject各自维护了一个队列,用来存放包装了线程的Node节点。联系上面的代码示例和源码可以总结一下AQS和ConditionObject中的队列是怎么被维护的:
(1) 示例中存在两个线程:main线程 t1线程
(2) 当main线程调用lock.locak()方法时,main获取到锁,继续执行, 当执行到t1.start()方法时,t1也想获取lock,但是此时该锁已经被main线程占用,所以t1线程进入 AQS维护的等待队列中,等待机会获取cpu的占用权。
(3) 当main线程执行了condition.await()方法时,main线程就在释放占用锁的同时加入到了ConditionObject维护的等待队列中,在这个队列中的线程,如果signal状态不发生改变,是永远没有机会获取到cpu的占有权的。
(4) 好,这个时候main已经进入了ConditionObject维护的等待队列中,那么AQS维护的等待队列中的t1线程就可以获取cpu的占有权了,可以继续执行。
(5) 当t1线程执行到 condition.signal() 方法时,就会唤醒ConditionObject维护的等待队列中的头节点,也就是main线程。但是注意,这里唤醒的意思是将main线程节点放到AQS维护的等待队列中,然后听从AQS的调度,并不是马上就能获取cpu的占有权。
(6) 然后t1线程执行结束,unlock释放占用的锁,在AQS维护的等待队列中的main就能继续执行下去了。
总之:
- ConditionObject维护的等待队列的功能是存放那些执行了await方法的线程,等待收到signal信息好可以进入AQS维护的等待队列中。在这个队列中是不会获取到锁的。
- AQS维护的等待队列存放那些就绪状态的线程,只等待目前占有锁的家伙执行完或者进入了ConditionObject维护的等待队列中之后,来进行竞争获取锁。只有在这个队列中才有资格(并不一定会)获取锁。
- 关于java线程浅谈五: Condition条件
- 关于java多线程浅析五: Condition条件
- java多线程--condition条件
- 线程--条件变量Condition Variable
- 关于实现条件condition-get
- Java多线程系列--Condition条件
- Java条件对象(Condition)
- 多线程之线程通信条件Condition
- 多线程之线程通信条件Condition二
- Java并发编程(五)--Condition
- Python 线程条件(Condition),(线程等待另一个线程的执行)
- java线程系列---condition例子
- java线程之Condition、ReentrantLock
- JAVA线程间协作:Condition
- java线程学习之Condition
- java 条件变量Condition——笔记
- 【java并发】条件阻塞Condition的应用
- java condition条件队列 生产者消费模式
- Angularjs 前端数据用orderBy排序,单表头和多表头处理,单表头不默认排序
- adb学习命令总结
- 将文件间的编译依存关系降至最低
- 《人类简史》读书笔记1
- 跟我一起写 Makefile(四)
- 关于java线程浅谈五: Condition条件
- C#网络编程(一、七层网络模型)
- 跟我一起写 Makefile(五)
- 浅拷贝与深拷贝
- 线程中的join方法
- SIFT
- JNI之HelloWorld
- 赤子之心李宗吾·《厚黑学全书》
- 跟我一起写 Makefile(六)