Java 多线程-多线程通信

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

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

/**
 * 定义一个盘子
 */
class Plate {

    // 栗子存储的空间
    private int[] cells = new int[10];

    // inPos表示存入的时候数组的下标,outPos表示取出的时候数组的下标
    private int inPos;
    private int outPos;

    // 定义一个put()方法向盘子中放栗子
    public void put(int num) {
        cells[inPos] = num;
        System.out.println("在cells[" + inPos + "]中放入了一个栗子----" + cells[inPos]);
        // 存完元素让位置+1
        inPos++;
        //当inPos为数组长度的时候,将其置为0
        if (inPos == cells.length) {
            inPos = 0;
        }
    }

    /**
     * 定义一个get()方法从盘子中取出栗子
     */
    public void get() {
        int data = cells[outPos];
        System.out.println("从cells[" + outPos + "]中取出了一个栗子" + data);
        // 取完后元素位置+1
        outPos++;
        if (outPos == cells.length) {
            outPos = 0;
        }
    }

}

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

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

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

/**
 * 帅帅类,剥栗子者也
 */
class ShuaiShuai implements Runnable {

    private Plate plate;
    // 定义一个变量num
    private int num;

    // 通过构造方法来接受一个盘子对象
    public ShuaiShuai(Plate plate) {
        this.plate = plate;
    }

    @Override
    public void run() {
        while (true) {
            // 将num存入数组,每次存入后num自增
            plate.put(num++);
        }
    }
}

/**
 * 美美类,吃栗子者也
 */
class MeiMei implements Runnable {

    private Plate plate;

    // 通过构造方法接收一个Plate对象
    MeiMei(Plate plate) {
        this.plate = plate;
    }

    @Override
    public void run() {
        while (true) {
            // 循环取出元素
            plate.get();
        }
    }
}

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

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

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

public class Demo {
    public static void main(String[] args) {

        // 创建一个盘子
        Plate plate = new Plate();

        // 创建帅帅对象和美美对象,并将盘子传入
        ShuaiShuai shuaiShuai = new ShuaiShuai(plate);
        MeiMei meiMei = new MeiMei(plate);

        // 开启两个线程
        new Thread(shuaiShuai).start();
        new Thread(meiMei).start();

    }
}

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

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

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

唤醒线程的方法:

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

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

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

/**
 * 定义一个盘子
 */
class Plate {

    // 栗子存储的空间
    private int[] cells = new int[1];

    // inPos表示存入的时候数组的下标,outPos表示取出的时候数组的下标
    private int inPos;
    private int outPos;

    // 存入或者取出数据的数量
    private int count;

    // 定义一个put()方法向盘子中放栗子
    public synchronized void put(int num) {
        try {
            // 如果数量等于数组的长度,此线程等待一下
            while (count == cells.length) {
                this.wait();
            }
            // 向盘子中放入栗子(想数组中放入数据)
            cells[inPos] = num;
            System.out.println("在cells[" + inPos + "]中放入了一个栗子----" + cells[inPos]);
            // 存完元素让位置+1
            inPos++;
            //当inPos为数组长度的时候,将其置为0
            if (inPos == cells.length) {
                inPos = 0;
            }
            // 放一个数据,count+1
            count++;
            this.notify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 定义一个get()方法从盘子中取出栗子
     */
    public synchronized void get() {
        try {
            // 如果count为0,没有栗子了,就等一下
            while (count == 0) {
                this.wait();
            }
            int data = cells[outPos];
            System.out.println("从cells[" + outPos + "]中取出了一个栗子" + data);
            // 取完后元素位置+1
            outPos++;
            if (outPos == cells.length) {
                outPos = 0;
            }
            // 取出一个数据,count-1
            count--;
            this.notify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值