Java 多线程-多线程通信

一时失言乱红尘 2022-10-23 12:54 361阅读 0赞

最近,美美非常的爱吃栗子,剥栗子却有些麻烦,这个任务理所当然的交给了帅帅,每一次,帅帅都会把热气腾腾的栗子剥好,然后放进一个盘子里,而美美每次都会从盘子里拿一个栗子吃:
在这里插入图片描述

我们来模拟一下这个情况,首先,我们定义一个盘子,用来存放我们的栗子:

  1. /** * 定义一个盘子 */
  2. class Plate {
  3. // 栗子存储的空间
  4. private int[] cells = new int[10];
  5. // inPos表示存入的时候数组的下标,outPos表示取出的时候数组的下标
  6. private int inPos;
  7. private int outPos;
  8. // 定义一个put()方法向盘子中放栗子
  9. public void put(int num) {
  10. cells[inPos] = num;
  11. System.out.println("在cells[" + inPos + "]中放入了一个栗子----" + cells[inPos]);
  12. // 存完元素让位置+1
  13. inPos++;
  14. //当inPos为数组长度的时候,将其置为0
  15. if (inPos == cells.length) {
  16. inPos = 0;
  17. }
  18. }
  19. /** * 定义一个get()方法从盘子中取出栗子 */
  20. public void get() {
  21. int data = cells[outPos];
  22. System.out.println("从cells[" + outPos + "]中取出了一个栗子" + data);
  23. // 取完后元素位置+1
  24. outPos++;
  25. if (outPos == cells.length) {
  26. outPos = 0;
  27. }
  28. }
  29. }

上面,我们定义了一个盘子,cells来存放数据(栗子),put方法用来向盘子中存放栗子,get 方法用于从盘子中获取数据。

针对数组元素的存取操作都是从第一个元素开始依次进行的,每当操作完数组的最后一个元素时,索引都会被置为0,也就是重新从数组的第一个位置开始存取元素。

接下来我们实现两个线程用来模拟两个人的操作,这两个线程都需要实现Runnable接口:

  1. /** * 帅帅类,剥栗子者也 */
  2. class ShuaiShuai implements Runnable {
  3. private Plate plate;
  4. // 定义一个变量num
  5. private int num;
  6. // 通过构造方法来接受一个盘子对象
  7. public ShuaiShuai(Plate plate) {
  8. this.plate = plate;
  9. }
  10. @Override
  11. public void run() {
  12. while (true) {
  13. // 将num存入数组,每次存入后num自增
  14. plate.put(num++);
  15. }
  16. }
  17. }
  18. /** * 美美类,吃栗子者也 */
  19. class MeiMei implements Runnable {
  20. private Plate plate;
  21. // 通过构造方法接收一个Plate对象
  22. MeiMei(Plate plate) {
  23. this.plate = plate;
  24. }
  25. @Override
  26. public void run() {
  27. while (true) {
  28. // 循环取出元素
  29. plate.get();
  30. }
  31. }
  32. }

上面的两个类,都实现了Runnable接口,并且构造方法中都接收了一个Plate类型的对象。

在这里插入图片描述
在帅帅的类ShuaiShuai中的run方法使用while循环不断的向存储空间中存放数据num,并且每次存入数据后将num进行自增,从而实现栗子1,栗子2,栗子3的效果。
在美美的类MeiMei中的run方法使用while循环不停的从存储空间中取出数据。

接下来我们写一个测试程序,开启两个线程分别运行MeiMei类和ShuaiShuai类中的代码,如下所示:

  1. public class Demo {
  2. public static void main(String[] args) {
  3. // 创建一个盘子
  4. Plate plate = new Plate();
  5. // 创建帅帅对象和美美对象,并将盘子传入
  6. ShuaiShuai shuaiShuai = new ShuaiShuai(plate);
  7. MeiMei meiMei = new MeiMei(plate);
  8. // 开启两个线程
  9. new Thread(shuaiShuai).start();
  10. new Thread(meiMei).start();
  11. }
  12. }

我们运行一下,发现了一个问题:
在这里插入图片描述
首先是当帅帅把栗子放入盘子中的时候,存在一个循环放置的问题,还没等美美将盘子拿走,已经将栗子又放了一遍,就覆盖掉了原来的栗子(好吧,这里暂且忽略它的荒谬);
其次,美美取出的时候,也是循环取出的,其实栗子已经拿出来了,但是还是又在同样的位置又拿了一遍(也忽略此处的荒谬性),这样就拿了一个寂寞;
而且,每次帅帅都是放入了一段时间了,美美才拿出的,热气腾腾的例子可能都放凉了(我们是模拟,现实中不可能帅帅几秒钟就剥了5万多个例子的,美美也不可能吃那么快,哈哈哈),可能会吃坏肚子。
在这里插入图片描述
如何解决这个问题呢?

通过前面的学习,我们知道可以通过锁来解决这个问题。
而我们想要控制多个线程按照一定的顺序轮流执行,这个时候就需要让线程间进行通信。

在object类中提供了wait()notify()notifyAll()方法用于解决线程间的通信问题,由于Java中所有类都是Object类的子类或者间接子类,因此任何类的实例对象都可以直接使用这些方法。

唤醒线程的方法:






















方法声明 功能描述
void wait() 是当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法,或notifyAll()方法唤醒该线程为止
void notify() 唤醒此同步锁上等待的第一个调用wait()方法的线程
void notifyAll() 唤醒此同步锁上调用wait()方法的所有线程

这三个方法调用的都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java虚拟机会抛出IllegalMonitorStateException异常。

接下来我们使用wait()方法和notify()方法,对例5-15进行改写来实现线程间的通信:

  1. /** * 定义一个盘子 */
  2. class Plate {
  3. // 栗子存储的空间
  4. private int[] cells = new int[1];
  5. // inPos表示存入的时候数组的下标,outPos表示取出的时候数组的下标
  6. private int inPos;
  7. private int outPos;
  8. // 存入或者取出数据的数量
  9. private int count;
  10. // 定义一个put()方法向盘子中放栗子
  11. public synchronized void put(int num) {
  12. try {
  13. // 如果数量等于数组的长度,此线程等待一下
  14. while (count == cells.length) {
  15. this.wait();
  16. }
  17. // 向盘子中放入栗子(想数组中放入数据)
  18. cells[inPos] = num;
  19. System.out.println("在cells[" + inPos + "]中放入了一个栗子----" + cells[inPos]);
  20. // 存完元素让位置+1
  21. inPos++;
  22. //当inPos为数组长度的时候,将其置为0
  23. if (inPos == cells.length) {
  24. inPos = 0;
  25. }
  26. // 放一个数据,count+1
  27. count++;
  28. this.notify();
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. /** * 定义一个get()方法从盘子中取出栗子 */
  34. public synchronized void get() {
  35. try {
  36. // 如果count为0,没有栗子了,就等一下
  37. while (count == 0) {
  38. this.wait();
  39. }
  40. int data = cells[outPos];
  41. System.out.println("从cells[" + outPos + "]中取出了一个栗子" + data);
  42. // 取完后元素位置+1
  43. outPos++;
  44. if (outPos == cells.length) {
  45. outPos = 0;
  46. }
  47. // 取出一个数据,count-1
  48. count--;
  49. this.notify();
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }

然后我们再测试一下:
在这里插入图片描述
此时的数据尽管没有那么的有规律,但是我们看到放入和取出的顺序起码是对的,每次从特定位置取出的栗子都是我们最后放入的那一个。
只要我们把盘子的大小改为1,就可以实现放一个拿一个的效果了。
在这里插入图片描述

发表评论

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

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

相关阅读

    相关 Java线04——线通信

    1 线程通信机制 线程通信指的是不同线程之间可以交换一些实时的数据信息。 线程是操作系统中的独立个体,但这些个体如果不经过特殊处理就不能成为一个整体,线程间的通信就成为

    相关 线12/线通信

    线程通信的例子:使用两个线程打印1-100。线程1,线程2交替打印 前置知识: 1. wait(); 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

    相关 Java 线-线通信

    最近,美美非常的爱吃栗子,剥栗子却有些麻烦,这个任务理所当然的交给了帅帅,每一次,帅帅都会把热气腾腾的栗子剥好,然后放进一个盘子里,而美美每次都会从盘子里拿一个栗子吃: !

    相关 线(3)- 线通信

    线程之间的通信: 多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。 等待唤醒机...