在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

一.wait()、notify()和notifyAll()

wait()、notify()和notifyAll()是Object类中的方法:

  1. public final native void notify();
  2. public final native void notifyAll();
  3. public final native void wait(long timeout) throws InterruptedException;

从这三个方法的文字描述可以知道以下几点信息:
1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。

同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。
举个简单的例子:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了。

上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

下面看一个例子就明白了:

  1. public class Test {
  2. public static Object object = new Object();
  3. public static void main(String[] args) {
  4. Thread1 thread1 = new Thread1();
  5. Thread2 thread2 = new Thread2();
  6.  
  7. thread1.start();
  8.  
  9. try {
  10. Thread.sleep();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14.  
  15. thread2.start();
  16. }
  17.  
  18. static class Thread1 extends Thread{
  19. @Override
  20. public void run() {
  21. synchronized (object) {
  22. try {
  23. object.wait();
  24. } catch (InterruptedException e) {
  25. }
  26. System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
  27. }
  28. }
  29. }
  30.  
  31. static class Thread2 extends Thread{
  32. @Override
  33. public void run() {
  34. synchronized (object) {
  35. object.notify();
  36. System.out.println("线程"+Thread.currentThread().getName()+"调用了object.notify()");
  37. }
  38. System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");
  39. }
  40. }
  41. }
  42.  
  43. 无论运行多少次,运行结果必定是:
  44. 线程Thread-1调用了object.notify()
  45. 线程Thread-1释放了锁
  46. 线程Thread-0获取到了锁

二.Condition

  Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

Conditon中的await()对应Object的wait();

Condition中的signal()对应Object的notify();

Condition中的signalAll()对应Object的notifyAll()。

三.生产者-消费者模型的实现

1.使用Object的wait()和notify()实现:

  1. public class Test {
  2. private int queueSize = ;
  3. private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
  4.  
  5. public static void main(String[] args) {
  6. Test test = new Test();
  7. Producer producer = test.new Producer();
  8. Consumer consumer = test.new Consumer();
  9.  
  10. producer.start();
  11. consumer.start();
  12. }
  13.  
  14. class Consumer extends Thread{
  15.  
  16. @Override
  17. public void run() {
  18. consume();
  19. }
  20.  
  21. private void consume() {
  22. while(true){
  23. synchronized (queue) {
  24. while(queue.size() == ){
  25. try {
  26. System.out.println("队列空,等待数据");
  27. queue.wait();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. queue.notify();
  31. }
  32. }
  33. queue.poll(); //每次移走队首元素
  34. queue.notify();
  35. System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
  36. }
  37. }
  38. }
  39. }
  40.  
  41. class Producer extends Thread{
  42.  
  43. @Override
  44. public void run() {
  45. produce();
  46. }
  47.  
  48. private void produce() {
  49. while(true){
  50. synchronized (queue) {
  51. while(queue.size() == queueSize){
  52. try {
  53. System.out.println("队列满,等待有空余空间");
  54. queue.wait();
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. queue.notify();
  58. }
  59. }
  60. queue.offer(); //每次插入一个元素
  61. queue.notify();
  62. System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
  63. }
  64. }
  65. }
  66. }
  67. }

2.使用Condition实现

  1. public class Test {
  2. private int queueSize = ;
  3. private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);
  4. private Lock lock = new ReentrantLock();
  5. private Condition notFull = lock.newCondition();
  6. private Condition notEmpty = lock.newCondition();
  7.  
  8. public static void main(String[] args) {
  9. Test test = new Test();
  10. Producer producer = test.new Producer();
  11. Consumer consumer = test.new Consumer();
  12.  
  13. producer.start();
  14. consumer.start();
  15. }
  16.  
  17. class Consumer extends Thread{
  18.  
  19. @Override
  20. public void run() {
  21. consume();
  22. }
  23.  
  24. private void consume() {
  25. while(true){
  26. lock.lock();
  27. try {
  28. while(queue.size() == ){
  29. try {
  30. System.out.println("队列空,等待数据");
  31. notEmpty.await();
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. queue.poll(); //每次移走队首元素
  37. notFull.signal();
  38. System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
  39. } finally{
  40. lock.unlock();
  41. }
  42. }
  43. }
  44. }
  45.  
  46. class Producer extends Thread{
  47.  
  48. @Override
  49. public void run() {
  50. produce();
  51. }
  52.  
  53. private void produce() {
  54. while(true){
  55. lock.lock();
  56. try {
  57. while(queue.size() == queueSize){
  58. try {
  59. System.out.println("队列满,等待有空余空间");
  60. notFull.await();
  61. } catch (InterruptedException e) {
  62. e.printStackTrace();
  63. }
  64. }
  65. queue.offer(); //每次插入一个元素
  66. notEmpty.signal();
  67. System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
  68. } finally{
  69. lock.unlock();
  70. }
  71. }
  72. }
  73. }
  74. }

Java并发编程(十三)线程间协作的两种方式:wait、notify、notifyAll和Condition的更多相关文章

  1. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  2. 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  3. 多线程之线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  4. Java并发--线程间协作的两种方式:wait、notify、notifyAll和Condition

    在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界 ...

  5. 线程间协作的两种方式:wait、notify、notifyAll和Condition

    转载自海子: 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者 ...

  6. 14 线程间协作的两种方式:wait、notify、notifyAll和Condition

    原文链接:http://www.cnblogs.com/dolphin0520/p/3920385.html 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者- ...

  7. Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  8. 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

  9. 并发编程 - 进程 - 1.开启子进程的两种方式/2.查看pid/3.Process对象的其他属性或方法/4.守护进程

    1.开启子进程的两种方式: # 方式1: from multiprocessing import Process import time def task(name): print('%s is ru ...

随机推荐

  1. Swift5 语言指南(十七) 反初始化

    一个deinitializer一个类的实例被释放之前立即调用.您使用deinit关键字编写deinitializers ,类似于使用init关键字编写初始化程序的方式.Deinitializers仅适 ...

  2. 【2019北京集训六】路径(path) 二分+DP

    此题niubi! 题目大意:给你一颗n个点的点带权无根树,现在请您进行以下两步操作: 1,选择一个$[0,T]$之间的整数$C$,并令所有的点权$wi$变为$(wi+C)%MOD$ 2,选择若干条点不 ...

  3. app测试环境搭建(python)

    app测试环境的搭建大致如下几个: 1.appium安装 appium-server或者使用appium-desktop都可以,前者已经不再更新 下载地址:appium.io 2.Android SD ...

  4. docker 容器时间和系统时间不一致

    docker cp /etc/localtime 容器名:/etc/localtime cp /etc/localtime 24fe94504424:/etc/localtime date -s 09 ...

  5. openshift上使用devicemapper

    环境:openshift v3.6.173.0.5 openshift上devicemapper与官方文档中的描述略有不同,在官方文档的描述中,容器使用的lvm文件系统挂载在/var/lib/devi ...

  6. 修复Nginx 502错误:upstream sent too big header while reading response header from upstream

    原文出处:https://www.cnblogs.com/jpfss/p/10237463.html 便于以后参考我复制了过来! cookies的值超出了范围我是说 看看了一下日志 错误502 ups ...

  7. JavaWeb学习 (二十四)————Filter(过滤器)常见应用

    一.统一全站字符编码 通过配置参数charset指明使用何种字符编码,以处理Html Form请求参数的中文问题 1 package me.gacl.web.filter; 2 3 import ja ...

  8. noip之后的一些感受

    你经历过绝望吗,那种希望完全破碎,眼前看不到光亮,不知道下一步怎么走,不知道接下来应该如何生活的那种绝望? 我经历过. 2018.11.12 下午 秦皇岛到石家庄的高铁上 听着同学兴高采烈的讨论,自己 ...

  9. 共识算法之POW

    简介 POW是proof-of-work的缩写,中译为:工作量证明,是比特币中采用的共识机制,也被许多公有区块链系统所采用(比如以太坊).工作量证明机制基础是哈希运算,因此要理解pow首先要明白哈希函 ...

  10. SQL语句简单笔记

    Create database database name://创建数据库 Show databases dbName: //显示所有数据库 Create table tableName: //创建表 ...