Java多线程:wait、notify(notifyAll)线程唤醒应用

雨点打透心脏的1/2处 2022-03-20 09:12 411阅读 0赞

前提概要

在Java的Object类中有三个final的方法允许线程之间进行资源对象锁的通信,他们分别是: wait(), notify() and notifyAll()。

调用这些方法的当前线程必须拥有此对象监视器,否则将会报java.lang.IllegalMonitorStateException exception异常。

  • wait
    Object的wait方法有三个重载方法,其中一个方法wait() 是无限期(一直)等待,直到其它线程调用notify或notifyAll方法唤醒当前的线程;另外两个方法wait(long timeout) 和wait(long timeout, int nanos)允许传入 当前线程在被唤醒之前需要等待的时间,timeout为毫秒数,nanos为纳秒数。
  • notify
    notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
  • notifyAll
    notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。

这些方法可以使用于“生产者-消费者”问题,消费者是在队列中等待对象的线程,生产者是在队列中释放对象并通知其他线程的线程。

在线程唤醒机制中如果处理不当会出现虚假唤醒(spurious wakeup)的情况,也就是说线程在等待信号时即使条件不满足,但是线程仍然可能被唤醒,解决这种问题的办法是在条件判断时使用while而不使用if,例如:

  1. while (num == 0) {
  2. System.out.println("现有鸡蛋:" + num + "颗,消费者不可消费!");
  3. this.wait();
  4. }

让我们来看一个多线程作用于同一个对象的例子,我们使用wait, notify and notifyAll方法。


实现代码

  1. package com.oumuv.producerandconsumer;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. /** * 生产者与消费者问题:wait、notify(notifyAll)应用 * 一个篮子可存20颗鸡蛋,鸡蛋数量小于5时生产者开始生产鸡蛋,篮子满后生产者停止生产并进入等待状态,鸡蛋数量等于0时消费者不能消费 * */
  5. public class ProducerAndConsumer2 {
  6. public static void main(String[] args) {
  7. ProducerAndConsumer2 pac = new ProducerAndConsumer2();
  8. EggKep eggKep = pac.new EggKep();
  9. Producer p1 = pac.new Producer(eggKep);
  10. Consumer c1 = pac.new Consumer(eggKep);
  11. ExecutorService executorService = Executors.newCachedThreadPool();
  12. executorService.submit(p1);
  13. executorService.submit(c1);
  14. }
  15. /** * 生产者 */
  16. class Producer implements Runnable {
  17. EggKep agg;
  18. public Producer(EggKep agg) {
  19. this.agg = agg;
  20. }
  21. @Override
  22. public void run() {
  23. try {
  24. for (int i = 0; i < 50; i++) {
  25. agg.produce();
  26. Thread.sleep(300);
  27. }
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. /** * 消费者 */
  34. class Consumer implements Runnable {
  35. EggKep agg;
  36. public Consumer(EggKep agg) {
  37. this.agg = agg;
  38. }
  39. @Override
  40. public void run() {
  41. try {
  42. for (int i = 0; i < 50; i++) {
  43. agg.consume();
  44. Thread.sleep(1000);
  45. }
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. }
  51. /** * 鸡蛋篮子 */
  52. class EggKep {
  53. int maxSize = 20;//篮子大小
  54. int num = 0;//鸡蛋数量
  55. boolean flag = true;//是否开始生产,默认true生产
  56. public EggKep(int maxSize, int num) {
  57. this.maxSize = maxSize;
  58. this.num = num;
  59. }
  60. public EggKep() {
  61. }
  62. /** *生产方法 */
  63. protected synchronized void produce() throws InterruptedException {
  64. //为了避免虚假唤醒,需要不断的检测满足条件
  65. while (!flag) {
  66. this.wait();
  67. }
  68. System.out.println("生产了一颗鸡蛋,现有鸡蛋:" + (++num) + "颗");
  69. if (num == maxSize) {
  70. flag = false;
  71. System.out.println("篮子满了,生产者停止工作!");
  72. }
  73. this.notify();
  74. }
  75. /** *消费方法 */
  76. protected synchronized void consume() throws InterruptedException {
  77. while (num == 0) {
  78. System.out.println("现有鸡蛋:" + num + "颗,消费者不可消费!");
  79. this.wait();
  80. }
  81. System.out.println("消费一颗鸡蛋,现有鸡蛋:" + (--num) + "颗");
  82. if (num <= 5 && !flag) {
  83. flag = true;
  84. System.out.println("通知生产者开始工作!");
  85. }
  86. this.notify();
  87. }
  88. }
  89. }

可能输出的结果

  1. 现有鸡蛋:0颗,消费者不可消费!
  2. 生产了一颗鸡蛋,现有鸡蛋:1
  3. 消费一颗鸡蛋,现有鸡蛋:0
  4. 生产了一颗鸡蛋,现有鸡蛋:1
  5. 生产了一颗鸡蛋,现有鸡蛋:2
  6. 生产了一颗鸡蛋,现有鸡蛋:3
  7. 消费一颗鸡蛋,现有鸡蛋:2
  8. 生产了一颗鸡蛋,现有鸡蛋:3
  9. 生产了一颗鸡蛋,现有鸡蛋:4
  10. 生产了一颗鸡蛋,现有鸡蛋:5
  11. 消费一颗鸡蛋,现有鸡蛋:4
  12. 生产了一颗鸡蛋,现有鸡蛋:5
  13. 生产了一颗鸡蛋,现有鸡蛋:6
  14. 生产了一颗鸡蛋,现有鸡蛋:7
  15. 消费一颗鸡蛋,现有鸡蛋:6
  16. 生产了一颗鸡蛋,现有鸡蛋:7
  17. 生产了一颗鸡蛋,现有鸡蛋:8
  18. 生产了一颗鸡蛋,现有鸡蛋:9
  19. 生产了一颗鸡蛋,现有鸡蛋:10
  20. 消费一颗鸡蛋,现有鸡蛋:9
  21. 生产了一颗鸡蛋,现有鸡蛋:10
  22. 生产了一颗鸡蛋,现有鸡蛋:11
  23. 生产了一颗鸡蛋,现有鸡蛋:12
  24. 消费一颗鸡蛋,现有鸡蛋:11
  25. 生产了一颗鸡蛋,现有鸡蛋:12
  26. 生产了一颗鸡蛋,现有鸡蛋:13
  27. 生产了一颗鸡蛋,现有鸡蛋:14
  28. 消费一颗鸡蛋,现有鸡蛋:13
  29. 生产了一颗鸡蛋,现有鸡蛋:14
  30. 生产了一颗鸡蛋,现有鸡蛋:15
  31. 生产了一颗鸡蛋,现有鸡蛋:16
  32. 生产了一颗鸡蛋,现有鸡蛋:17
  33. 消费一颗鸡蛋,现有鸡蛋:16
  34. 生产了一颗鸡蛋,现有鸡蛋:17
  35. 生产了一颗鸡蛋,现有鸡蛋:18
  36. 生产了一颗鸡蛋,现有鸡蛋:19
  37. 消费一颗鸡蛋,现有鸡蛋:18
  38. 生产了一颗鸡蛋,现有鸡蛋:19
  39. 生产了一颗鸡蛋,现有鸡蛋:20
  40. 篮子满了,生产者停止工作!
  41. 消费一颗鸡蛋,现有鸡蛋:19
  42. 消费一颗鸡蛋,现有鸡蛋:18
  43. 消费一颗鸡蛋,现有鸡蛋:17
  44. 消费一颗鸡蛋,现有鸡蛋:16
  45. 消费一颗鸡蛋,现有鸡蛋:15
  46. 消费一颗鸡蛋,现有鸡蛋:14
  47. 消费一颗鸡蛋,现有鸡蛋:13
  48. 消费一颗鸡蛋,现有鸡蛋:12
  49. 消费一颗鸡蛋,现有鸡蛋:11
  50. 消费一颗鸡蛋,现有鸡蛋:10
  51. 消费一颗鸡蛋,现有鸡蛋:9
  52. 消费一颗鸡蛋,现有鸡蛋:8
  53. 消费一颗鸡蛋,现有鸡蛋:7
  54. 消费一颗鸡蛋,现有鸡蛋:6
  55. 消费一颗鸡蛋,现有鸡蛋:5
  56. 通知生产者开始工作!
  57. 生产了一颗鸡蛋,现有鸡蛋:6
  58. 生产了一颗鸡蛋,现有鸡蛋:7
  59. 生产了一颗鸡蛋,现有鸡蛋:8
  60. 生产了一颗鸡蛋,现有鸡蛋:9
  61. 消费一颗鸡蛋,现有鸡蛋:8
  62. 生产了一颗鸡蛋,现有鸡蛋:9
  63. 生产了一颗鸡蛋,现有鸡蛋:10
  64. 生产了一颗鸡蛋,现有鸡蛋:11
  65. 消费一颗鸡蛋,现有鸡蛋:10
  66. 生产了一颗鸡蛋,现有鸡蛋:11
  67. 生产了一颗鸡蛋,现有鸡蛋:12
  68. 生产了一颗鸡蛋,现有鸡蛋:13
  69. 消费一颗鸡蛋,现有鸡蛋:12
  70. .....

代码托管:https://github.com/Oumuv/ThreadDemo

发表评论

表情:
评论列表 (有 0 条评论,411人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java 线中虚假唤醒

    虚假唤醒详解 接触过多线程编程的朋友们或多或少都听说过虚假唤醒这一术语,我在百度,B 站上看了很多讲解,都没说的太清楚,在这里写一下,尽量规范一下相关概念。 虚假唤醒