Java多线程---线程间的通信(协作)

ゞ 浴缸里的玫瑰 2022-04-13 10:21 468阅读 0赞

转自:http://josh-persistence.iteye.com/blog/1924725
在很多情况下,多线程间仅仅同步是不够的,还需要线程与线程协作(通信),生产者/消费者模式是一个经典的线程同步以及通信的模型。

假设有这样一种情况,有一个篮子,篮子里只能放一个鸡蛋,A线程专门往篮子里放鸡蛋,如果篮子里有鸡蛋,则一直等到篮子里没鸡蛋,B线程专门从篮子里取鸡蛋,如果篮子里没鸡蛋,则一直等到篮子里有鸡蛋。这里篮子是一个互斥区,每次放鸡蛋是互斥的,每次取鸡蛋也是互斥的,A线程放鸡蛋,如果这时B线程要取鸡蛋,由于A没有释放锁,B线程处于等待状态,进入阻塞队列,放鸡蛋之后,要通知B线程取鸡蛋,B线程进入就绪队列,反过来,B线程取鸡蛋,如果A线程要放 鸡蛋,由于B线程没有释放锁,A线程处于等待状态,进入阻塞队列,取鸡蛋之后,要通知A线程放鸡蛋,A线程进入就绪队列。我们希望当篮子里有鸡蛋时,A线程阻塞,B线程就绪,篮子里没鸡蛋时,A线程就绪,B线程阻塞,代码如下:

  1. /**
  2. *
  3. */
  4. package com.wsheng.thread.communication;
  5. /**
  6. * 鸡蛋类
  7. *
  8. * @author Wang Sheng(Josh)
  9. *
  10. */
  11. public class Egg {
  12. private double weight;
  13. private double price;
  14. public double getWeight() {
  15. return weight;
  16. }
  17. public void setWeight(double weight) {
  18. this.weight = weight;
  19. }
  20. public double getPrice() {
  21. return price;
  22. }
  23. public void setPrice(double price) {
  24. this.price = price;
  25. }
  26. }
  27. /**
  28. *
  29. */
  30. package com.wsheng.thread.communication;
  31. import java.util.ArrayList;
  32. import java.util.List;
  33. /**
  34. * 线程间的通信: 放鸡蛋和取鸡蛋 - 生产者和消费者
  35. * @author Wang Sheng(Josh)
  36. *
  37. */
  38. public class Basket {
  39. /** 共享资源:篮子 */
  40. private List<Egg> eggs = new ArrayList<Egg>();
  41. /** 取鸡蛋*/
  42. public synchronized Egg getEgg() {
  43. while (eggs.size() == 0) {
  44. try {
  45. wait(); // 当前线程进入阻塞队列
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. Egg egg = eggs.get(0);
  51. // 清空篮子
  52. eggs.clear();
  53. // 唤醒阻塞队列的某线程到就绪队列
  54. notify();
  55. System.out.println("拿到鸡蛋");
  56. return egg;
  57. }
  58. /** 放鸡蛋 */
  59. public synchronized void putEgg(Egg egg) {
  60. while (eggs.size() > 0) {
  61. try {
  62. wait();
  63. } catch (InterruptedException e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. eggs.add(egg);
  68. // 唤醒阻塞队列的某线程到就绪队列
  69. notify();
  70. System.out.println("放入鸡蛋");
  71. }
  72. static class PutThread extends Thread {
  73. private Basket basket;
  74. private Egg egg = new Egg();
  75. public PutThread(Basket basket) {
  76. this.basket = basket;
  77. }
  78. public void run() {
  79. basket.putEgg(egg);
  80. }
  81. }
  82. static class GetThread extends Thread {
  83. private Basket basket;
  84. public GetThread(Basket basket) {
  85. this.basket = basket;
  86. }
  87. public void run() {
  88. basket.getEgg();
  89. }
  90. }
  91. public static void main(String[] args) {
  92. Basket basket = new Basket();
  93. for (int i = 0; i < 10; i++) {
  94. new PutThread(basket).start();
  95. new GetThread(basket).start();
  96. }
  97. }
  98. }

输出结果:
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋

程序开始,A线程判断篮子是否为空,放入一个鸡蛋,并且唤醒在阻塞队列的一个线程,阻塞队列为空;假设CPU又调度了一个A线程,篮子非空,执行等待,这 个A线程进入阻塞队列;然后一个B线程执行,篮子非空,取走鸡蛋,并唤醒阻塞队列的A线程,A线程进入就绪队列,此时就绪队列就一个A线程,马上执行,放 入鸡蛋;如果再来A线程重复第一步,再来B线程重复第二步,整个过程就是生产者(A线程)生产鸡蛋,消费者(B线程)消费鸡蛋。

这个例子中,同步(互斥)的资源是一个实实在在的篮子对象,其实,共享资源也可以是一个简单的变量,如下面的例子,共享资源是一个bool变量。
例: 子线程循环10次,然后主线程循环100次,如此循环100次。子循环执行时主循环不能执行,主循环执行时子循环也不能执行。

  1. /**
  2. *
  3. */
  4. package com.wsheng.thread.synchronize;
  5. /**
  6. * @author Wang Sheng(Josh)
  7. *
  8. */
  9. public class LoopThreadTest {
  10. public static void main(String[] args) {
  11. final Loop loopObj = new Loop();
  12. new Thread(new Runnable() {
  13. public void run() {
  14. execute(loopObj, "sub");
  15. }
  16. }).start();
  17. execute(loopObj, "main");
  18. }
  19. public static void execute(Loop loop, String threadType) {
  20. for (int i = 0; i < 100; i++) {
  21. try {
  22. if ("main".equals(threadType)) {
  23. loop.mainThread(i);
  24. } else {
  25. loop.subThread(i);
  26. }
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. }
  33. class Loop {
  34. private boolean isSubThread = true;
  35. public synchronized void mainThread(int loop) throws InterruptedException {
  36. while (isSubThread) {
  37. this.wait();
  38. }
  39. for (int i = 0; i < 100; i++) {
  40. System.out.println("main thread sequence of " + i + ", loop of " + loop);
  41. }
  42. isSubThread = true; // 主线程执行完毕,由子线程来执行
  43. this.notify(); // 唤醒阻塞队列中的子线程到就绪队列
  44. }
  45. public synchronized void subThread(int loop) throws InterruptedException {
  46. while (!isSubThread) {
  47. this.wait();
  48. }
  49. for (int i = 0; i < 10; i++) {
  50. System.out.println("sub thread sequence of " + i + ", loop of " + loop);
  51. }
  52. isSubThread = false; // 子线程执行完毕,由主线程来执行
  53. this.notify(); // 唤醒阻塞队列中的主线程到就绪队列
  54. }
  55. }

需要注意的是,在上面的例子中,在调用wait方法时,都是用while判断条件的,而不是if,在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥。

发表评论

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

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

相关阅读