概要

前面对JUC包中的锁的原理进行了介绍,本章会JUC中对与锁经常配合使用的Condition进行介绍,内容包括:
Condition介绍
Condition函数列表
Condition示例
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3496716.html

Condition介绍

Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

Condition函数列表

  1. // 造成当前线程在接到信号或被中断之前一直处于等待状态。
  2. void await()
  3. // 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  4. boolean await(long time, TimeUnit unit)
  5. // 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  6. long awaitNanos(long nanosTimeout)
  7. // 造成当前线程在接到信号之前一直处于等待状态。
  8. void awaitUninterruptibly()
  9. // 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
  10. boolean awaitUntil(Date deadline)
  11. // 唤醒一个等待线程。
  12. void signal()
  13. // 唤醒所有等待线程。
  14. void signalAll()

Condition示例

示例1是通过Object的wait(), notify()来演示线程的休眠/唤醒功能。
示例2是通过Condition的await(), signal()来演示线程的休眠/唤醒功能。
示例3是通过Condition的高级功能。

示例1

  1. public class WaitTest1 {
  2.  
  3. public static void main(String[] args) {
  4.  
  5. ThreadA ta = new ThreadA("ta");
  6.  
  7. synchronized(ta) { // 通过synchronized(ta)获取“对象ta的同步锁”
  8. try {
  9. System.out.println(Thread.currentThread().getName()+" start ta");
  10. ta.start();
  11.  
  12. System.out.println(Thread.currentThread().getName()+" block");
  13. ta.wait(); // 等待
  14.  
  15. System.out.println(Thread.currentThread().getName()+" continue");
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21.  
  22. static class ThreadA extends Thread{
  23.  
  24. public ThreadA(String name) {
  25. super(name);
  26. }
  27.  
  28. public void run() {
  29. synchronized (this) { // 通过synchronized(this)获取“当前对象的同步锁”
  30. System.out.println(Thread.currentThread().getName()+" wakup others");
  31. notify(); // 唤醒“当前对象上的等待线程”
  32. }
  33. }
  34. }
  35. }

示例2

  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. public class ConditionTest1 {
  6.  
  7. private static Lock lock = new ReentrantLock();
  8. private static Condition condition = lock.newCondition();
  9.  
  10. public static void main(String[] args) {
  11.  
  12. ThreadA ta = new ThreadA("ta");
  13.  
  14. lock.lock(); // 获取锁
  15. try {
  16. System.out.println(Thread.currentThread().getName()+" start ta");
  17. ta.start();
  18.  
  19. System.out.println(Thread.currentThread().getName()+" block");
  20. condition.await(); // 等待
  21.  
  22. System.out.println(Thread.currentThread().getName()+" continue");
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. } finally {
  26. lock.unlock(); // 释放锁
  27. }
  28. }
  29.  
  30. static class ThreadA extends Thread{
  31.  
  32. public ThreadA(String name) {
  33. super(name);
  34. }
  35.  
  36. public void run() {
  37. lock.lock(); // 获取锁
  38. try {
  39. System.out.println(Thread.currentThread().getName()+" wakup others");
  40. condition.signal(); // 唤醒“condition所在锁上的其它线程”
  41. } finally {
  42. lock.unlock(); // 释放锁
  43. }
  44. }
  45. }
  46. }

运行结果

  1. main start ta
  2. main block
  3. ta wakup others
  4. main continue

通过“示例1”和“示例2”,我们知道Condition和Object的方法有一下对应关系:

  1. Object Condition
  2. 休眠 wait await
  3. 唤醒个线程 notify signal
  4. 唤醒所有线程 notifyAll signalAll

Condition除了支持上面的功能之外,它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,"读线程"需要等待。         如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程",而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。  但是,通过Condition,就能明确的指定唤醒读线程。
看看下面的示例3,可能对这个概念有更深刻的理解。

示例3

  1. import java.util.concurrent.locks.Lock;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.ReentrantLock;
  4.  
  5. class BoundedBuffer {
  6. final Lock lock = new ReentrantLock();
  7. final Condition notFull = lock.newCondition();
  8. final Condition notEmpty = lock.newCondition();
  9.  
  10. final Object[] items = new Object[5];
  11. int putptr, takeptr, count;
  12.  
  13. public void put(Object x) throws InterruptedException {
  14. lock.lock(); //获取锁
  15. try {
  16. // 如果“缓冲已满”,则等待;直到“缓冲”不是满的,才将x添加到缓冲中。
  17. while (count == items.length)
  18. notFull.await();
  19. // 将x添加到缓冲中
  20. items[putptr] = x;
  21. // 将“put统计数putptr+1”;如果“缓冲已满”,则设putptr为0。
  22. if (++putptr == items.length) putptr = 0;
  23. // 将“缓冲”数量+1
  24. ++count;
  25. // 唤醒take线程,因为take线程通过notEmpty.await()等待
  26. notEmpty.signal();
  27.  
  28. // 打印写入的数据
  29. System.out.println(Thread.currentThread().getName() + " put "+ (Integer)x);
  30. } finally {
  31. lock.unlock(); // 释放锁
  32. }
  33. }
  34.  
  35. public Object take() throws InterruptedException {
  36. lock.lock(); //获取锁
  37. try {
  38. // 如果“缓冲为空”,则等待;直到“缓冲”不为空,才将x从缓冲中取出。
  39. while (count == 0)
  40. notEmpty.await();
  41. // 将x从缓冲中取出
  42. Object x = items[takeptr];
  43. // 将“take统计数takeptr+1”;如果“缓冲为空”,则设takeptr为0。
  44. if (++takeptr == items.length) takeptr = 0;
  45. // 将“缓冲”数量-1
  46. --count;
  47. // 唤醒put线程,因为put线程通过notFull.await()等待
  48. notFull.signal();
  49.  
  50. // 打印取出的数据
  51. System.out.println(Thread.currentThread().getName() + " take "+ (Integer)x);
  52. return x;
  53. } finally {
  54. lock.unlock(); // 释放锁
  55. }
  56. }
  57. }
  58.  
  59. public class ConditionTest2 {
  60. private static BoundedBuffer bb = new BoundedBuffer();
  61.  
  62. public static void main(String[] args) {
  63. // 启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9);
  64. // 启动10个“读线程”,从BoundedBuffer中不断的读数据。
  65. for (int i=0; i<10; i++) {
  66. new PutThread("p"+i, i).start();
  67. new TakeThread("t"+i).start();
  68. }
  69. }
  70.  
  71. static class PutThread extends Thread {
  72. private int num;
  73. public PutThread(String name, int num) {
  74. super(name);
  75. this.num = num;
  76. }
  77. public void run() {
  78. try {
  79. Thread.sleep(1); // 线程休眠1ms
  80. bb.put(num); // 向BoundedBuffer中写入数据
  81. } catch (InterruptedException e) {
  82. }
  83. }
  84. }
  85.  
  86. static class TakeThread extends Thread {
  87. public TakeThread(String name) {
  88. super(name);
  89. }
  90. public void run() {
  91. try {
  92. Thread.sleep(10); // 线程休眠1ms
  93. Integer num = (Integer)bb.take(); // 从BoundedBuffer中取出数据
  94. } catch (InterruptedException e) {
  95. }
  96. }
  97. }
  98. }

(某一次)运行结果

  1. p1 put 1
  2. p4 put 4
  3. p5 put 5
  4. p0 put 0
  5. p2 put 2
  6. t0 take 1
  7. p3 put 3
  8. t1 take 4
  9. p6 put 6
  10. t2 take 5
  11. p7 put 7
  12. t3 take 0
  13. p8 put 8
  14. t4 take 2
  15. p9 put 9
  16. t5 take 3
  17. t6 take 6
  18. t7 take 7
  19. t8 take 8
  20. t9 take 9

结果说明
(01) BoundedBuffer 是容量为5的缓冲,缓冲中存储的是Object对象,支持多线程的读/写缓冲。多个线程操作“一个BoundedBuffer对象”时,它们通过互斥锁lock对缓冲区items进行互斥访问;而且同一个BoundedBuffer对象下的全部线程共用“notFull”和“notEmpty”这两个Condition。
       notFull用于控制写缓冲,notEmpty用于控制读缓冲。当缓冲已满的时候,调用put的线程会执行notFull.await()进行等待;当缓冲区不是满的状态时,就将对象添加到缓冲区并将缓冲区的容量count+1,最后,调用notEmpty.signal()缓冲notEmpty上的等待线程(调用notEmpty.await的线程)。 简言之,notFull控制“缓冲区的写入”,当往缓冲区写入数据之后会唤醒notEmpty上的等待线程。
       同理,notEmpty控制“缓冲区的读取”,当读取了缓冲区数据之后会唤醒notFull上的等待线程。
(02) 在ConditionTest2的main函数中,启动10个“写线程”,向BoundedBuffer中不断的写数据(写入0-9);同时,也启动10个“读线程”,从BoundedBuffer中不断的读数据。
(03) 简单分析一下运行结果。

  1. 1, p1线程向缓冲中写入1 此时,缓冲区数据: | 1 | | | | |
  2. 2, p4线程向缓冲中写入4 此时,缓冲区数据: | 1 | 4 | | | |
  3. 3, p5线程向缓冲中写入5 此时,缓冲区数据: | 1 | 4 | 5 | | |
  4. 4, p0线程向缓冲中写入0 此时,缓冲区数据: | 1 | 4 | 5 | 0 | |
  5. 5, p2线程向缓冲中写入2 此时,缓冲区数据: | 1 | 4 | 5 | 0 | 2 |
  6. 此时,缓冲区容量为5;缓冲区已满!如果此时,还有“写线程”想往缓冲中写入数据,会调用put中的notFull.await()等待,直接缓冲区非满状态,才能继续运行。
  7. 6, t0线程从缓冲中取出数据1。此时,缓冲区数据: | | 4 | 5 | 0 | 2 |
  8. 7, p3线程向缓冲中写入3 此时,缓冲区数据: | 3 | 4 | 5 | 0 | 2 |
  9. 8, t1线程从缓冲中取出数据4。此时,缓冲区数据: | 3 | | 5 | 0 | 2 |
  10. 9, p6线程向缓冲中写入6 此时,缓冲区数据: | 3 | 6 | 5 | 0 | 2 |
  11. ...

Java - "JUC"之Condition源码解析的更多相关文章

  1. Java集合---Array类源码解析

    Java集合---Array类源码解析              ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...

  2. java.lang.Void类源码解析_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerEx ...

  3. Java并发之ReentrantLock源码解析(四)

    Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...

  4. Java并发之ThreadPoolExecutor源码解析(二)

    ThreadPoolExecutor ThreadPoolExecutor是ExecutorService的一种实现,可以用若干已经池化的线程执行被提交的任务.使用线程池可以帮助我们限定和整合程序资源 ...

  5. Java并发之ReentrantLock源码解析(二)

    在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...

  6. Java集合类:AbstractCollection源码解析

    一.Collection接口 从<Java集合:整体结构>一文中我们知道所有的List和Set都继承自Collection接口,该接口类提供了集合最基本的方法,虽然List接口和Set等都 ...

  7. Java集合:LinkedList源码解析

    Java集合---LinkedList源码解析   一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据re ...

  8. Java并发之Semaphore源码解析(一)

    Semaphore 前情提要:在学习本章前,需要先了解笔者先前讲解过的ReentrantLock源码解析,ReentrantLock源码解析里介绍的方法有很多是本章的铺垫.下面,我们进入本章正题Sem ...

  9. Java并发之Semaphore源码解析(二)

    在上一章,我们学习了信号量(Semaphore)是如何请求许可证的,下面我们来看看要如何归还许可证. 可以看到当我们要归还许可证时,不论是调用release()或是release(int permit ...

随机推荐

  1. 知物由学 | AI在Facebook清理有害内容上扮演了什么角色?

    "知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道."知物 ...

  2. <转>php中heredoc与nowdoc的使用方法

    http://www.361way.com/php-heredoc-nowdoc/3008.html 一.heredoc结构及用法 Heredoc 结构就象是没有使用双引号的双引号字符串,这就是说在 ...

  3. Python语法基础练习

  4. MyEclipse 编写 JSP 代码时很卡的解决办法

    在网上看到很多方法,都是尝试过,个人感觉都没有说到重点,所以收效甚微. 后来自己总结了一下: 我们都是习惯在MyEclipse 工具,双击jsp 文件打开进行编辑.这时,工具会打开窗口的 Previe ...

  5. javascript数据结构与算法--高级排序算法(快速排序法,希尔排序法)

    javascript数据结构与算法--高级排序算法(快速排序法,希尔排序法) 一.快速排序算法 /* * 这个函数首先检查数组的长度是否为0.如果是,那么这个数组就不需要任何排序,函数直接返回. * ...

  6. gradle构建工具

    在使用android studio开发android程序时,as就是基于gradle进行构建的,我们只需要通过run就可以编译.打包.安装,非常方便,但是究竟gradle是什么呢?  一.java构建 ...

  7. 03-03:springBoot 整合thymeleaf

    thymeleaf 语法详解1.变量输出: th:text :在页面中输出某个值 th:value :将一个值放到input标签中的value中.2.判断字符串是否为空 ①:调用内置对象一定要用# ② ...

  8. 一学期积累下来的SQL语句写法的学习

    整合了一下上学期学习的积累,希望可以帮到初学者! 可能以后会有用吧! A 基本语句的运用 操作基于emp表1.按工资从高到低排列SQL> select rownum as 次序,ename,sa ...

  9. 高可用Hadoop平台-实战尾声篇

    1.概述 今天这篇博客就是<高可用Hadoop平台>的尾声篇了,从搭建安装到入门运行 Hadoop 版的 HelloWorld(WordCount 可以称的上是 Hadoop 版的 Hel ...

  10. Linux进程间的通信

    一.管道 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: A. 管道是半双工的,数据只能向一个方向流动: B. 需要双工通信时,需要建立起两个管道: C. 只能用于父子进程或者兄弟 ...