java 中 wait 和 notify 线程通信举例说明

测试账号 2021-01-19 17:13 553阅读 0赞

前言:

等待/通知机制主要由Object类中的wait()、notify() 和 notifyAll()三个方法来实现,这三个方法均非Thread类中所声明的方法,而是Object类中声明的方法;原因是每个对象都拥有monitor(锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作,而不是用当前线程来操作

wait()——让当前线程释放对象锁并进入等待(阻塞)状态,在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait方法。

notify()——唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行,和wait方法一样,线程也必须获得该对象的对象级别锁,也只能在同步方法或同步块中调用。

notifyAll()——唤醒所有正在等待相应对象锁的所有线程,其它和notify一样。

一、举例说明

这里举一个生产面包和销售面包的例子,代码如下

  1. 声明一个面包类

    public class Bread {

    1. public String name;
    2. public int num;
    3. Bread(String name, int num) {
    4. System.out.println("面包名称:" + name + ", 初始化数量:" + num);
    5. this.name = name;
    6. this.num = num;
    7. }

    }

  2. 声明一个生产面包的类,produce() 方法每次调用会生产一个面包,其调用受run()方法控制。

    public class BreadProducer implements Runnable {

    1. private final Bread bread;
    2. private int maxNum;
    3. BreadProducer(Bread bread, int maxNum) {
    4. this.bread = bread;
    5. this.maxNum = maxNum;
    6. }
    7. public void produce() {
    8. bread.num = bread.num + 1;
    9. System.out.println("面包生产1个,当前数量:" + bread.num);
    10. try {
    11. Thread.sleep(1000);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. public void run() {
    17. synchronized (bread) {
    18. if (bread.num >= maxNum) {
    19. System.out.println("面包生产数量达到巅峰,停止生产,等待卖出");
    20. try {
    21. System.out.println("***************");
    22. bread.wait();
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. } else {
    27. System.out.println("---------------");
    28. produce();
    29. bread.notifyAll(); // 调用生产方法以后会唤醒wait等待的线程
    30. }
    31. }
    32. }

    }

  1. 声明一个售卖面包的类,sale() 方法每次调用会销售一个面包,其调用受run()控制

    public class BreadSale implements Runnable {

    1. private final Bread bread;
    2. BreadSale(Bread bread) {
    3. this.bread = bread;
    4. }
    5. private void sale() {
    6. bread.num = bread.num - 1;
    7. System.out.println("面包售卖一个,当前剩余:" + bread.num);
    8. try {
    9. Thread.sleep(1000);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. }
    14. public void run() {
    15. synchronized (bread) {
    16. if (bread.num <= 0) {
    17. System.out.println("面包已卖完,等待生产");
    18. try {
    19. bread.wait();
    20. } catch (InterruptedException e) {
    21. e.printStackTrace();
    22. }
    23. } else {
    24. sale();
    25. bread.notifyAll(); // 调用销售方法以后会唤醒wait等待的线程
    26. }
    27. }
    28. }

    }

  2. 写一个测试方法

    package thread.wait;

    public class Main {

    1. public static void main(String[] args) throws InterruptedException {
    2. Bread bread = new Bread("土司", 1);
    3. BreadProducer breadProducer = new BreadProducer(bread, 2);
    4. BreadSale breadSale = new BreadSale(bread);
    5. Thread tp = new Thread(breadProducer);
    6. Thread ts = new Thread(breadSale);
    7. ts.start();
    8. tp.start();
    9. }

    }

结果(两个线程启动顺序不同结果不同):

  1. 面包名称:土司, 初始化数量:1
  2. 面包售卖一个,当前剩余:0
  3. ---------------
  4. 面包生产1个,当前数量:1

根据上面的输出,可以判断:

1). 线程ts先启动执行,调用 BreadSale 中的run()方法,run()方法调用 sale() 方法,而后唤醒所有等待bread锁的进程(看结果唤醒的是 tp 进程),而后该线程逐步结束

2). 线程tp启动后调用 BreadProducer 中的run()方法,run()方法调用 produce() 方法,而后唤醒所有等待bread锁的进程(这时候tp已经结束,没有线程等待,所以直接结束了)

二、举例延申

考虑到上述是生产面包售卖面包是一个持续的变化过程(即通常描述的生产-消费模式),可以对两个多线程类稍加改造,加入while(true) 的循环,这样就能一直持续这种生产-销售过程。

  1. public class BreadProducer implements Runnable {
  2. private final Bread bread;
  3. private int maxNum;
  4. BreadProducer(Bread bread, int maxNum) {
  5. this.bread = bread;
  6. this.maxNum = maxNum;
  7. }
  8. public void produce() {
  9. bread.num = bread.num + 1;
  10. System.out.println("面包生产1个,当前数量:" + bread.num);
  11. try {
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. public void run() {
  18. synchronized (bread) {
  19. while (true) {
  20. if (bread.num >= maxNum) {
  21. System.out.println("面包生产数量达到巅峰,停止生产,等待卖出");
  22. try {
  23. System.out.println("***************");
  24. bread.wait();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. } else {
  29. System.out.println("---------------");
  30. produce();
  31. bread.notifyAll();
  32. }
  33. }
  34. }
  35. }
  36. }
  37. public class BreadSale implements Runnable {
  38. private final Bread bread;
  39. BreadSale(Bread bread) {
  40. this.bread = bread;
  41. }
  42. private void sale() {
  43. bread.num = bread.num - 1;
  44. System.out.println("面包售卖一个,当前剩余:" + bread.num);
  45. try {
  46. Thread.sleep(1000);
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. public void run() {
  52. synchronized (bread) {
  53. while (true) {
  54. if (bread.num <= 0) {
  55. System.out.println("面包已卖完,等待生产");
  56. try {
  57. bread.wait();
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. } else {
  62. sale();
  63. bread.notifyAll();
  64. }
  65. }
  66. }
  67. }
  68. }
  69. public class Main {
  70. public static void main(String[] args) throws InterruptedException {
  71. Bread bread = new Bread("土司", 3);
  72. BreadProducer breadProducer = new BreadProducer(bread, 5);
  73. BreadSale breadSale = new BreadSale(bread);
  74. Thread tp = new Thread(breadProducer);
  75. Thread ts = new Thread(breadSale);
  76. ts.start();
  77. tp.start();
  78. }
  79. }

启动 main() 函数以后,可以看到一个持续生产-售卖的过程,永不停息(注意:慎用死循环

发表评论

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

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

相关阅读

    相关 线通信 waitnotify

    一、概念   线程是操作系统中独立的个体,这些线程如果不通过特殊的手段进行处理,就无法组成一个完整的整体。因此线程通信就称为组成一个整体的必须条件之一。当线程之间存在通