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,就可以实现放一个拿一个的效果了。
还没有评论,来说两句吧...