示例讲解:
本示例模拟中生产者由“厨师”担任,消费者由“顾客担任”,模拟做菜与上菜的一个简单操作。类之间的关系如图:
初始代码块分解:
Food类为食物类,内置了set(Food food)生产食物和get(Food food)消费取走食物方法,Food类有两个属性分别是Name(菜品名称)、Desc(菜品描述)并对其进行属性封装;休眠操作模拟了菜品制作以及取出所花费的时间。
/**
* 食物类
*/
class Food {
private String Name;//菜品名称
private String Desc;//菜品描述
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getDesc() {
return Desc;
}
public void setDesc(String desc) {
Desc = desc;
}
@Override
public String toString() {
return "Food{" +
"Name='" + Name + '\'' +
", Desc='" + Desc + '\'' +
'}';
}
public Food() {
}
public Food(String name, String desc) {
Name = name;
Desc = desc;
}
/*
* 生产食物set
* */
public void set(String Name, String Desc) {
this.setName(Name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(Desc);
}
/*
* 消费食物get
* */
public void get() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + "-->" + this.getDesc());
}
}
Producter类为生产者类,实现了现成的Runnable接口 ,在此类中会生成两种菜品“糖醋里脊”、“辣子鸡丁”,奇数为前者,偶数为后者。
*
* 生产对象类
* */
class Producter implements Runnable {
private Food food;
public Producter(Food food) {
this.food = food;
}
public Producter() {
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
food.set("糖醋里脊", "酸甜口味");
} else {
food.set("辣子鸡丁", "麻辣口味");
}
}
}
}
Customers类为消费者类,同样实现了线程的Runnable接口,在此类会逐步取出所有菜品;
/*
* 消费者对象Customers
* */
class Customers implements Runnable {
private Food food;
public Customers() {
}
public Customers(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
food.get();
}
}
}
测试类ProducterCustomerDemo,开启生产者、消费者这两个线程;
/**
* Description: thread
* Created by WuHuaSen .
* Created Date: 2022/3/20 10:02
* Version: V1.0
*/
public class ProducterCustomerDemo {
public static void main(String[] args) {
Food food = new Food();
Producter producter = new Producter(food);
Customers customers = new Customers(food);
Thread t1 = new Thread(producter);
Thread t2 = new Thread(customers);
t1.start();
t2.start();
}
}
测试结果:
通过测试结果会发现,菜品和对菜品的描述出现了混淆输出,并且对于生产者“糖醋里脊”、“辣子鸡丁”是交互输出,而测试结果出现了“辣子鸡丁”、“糖醋里脊”连续输出的情况,鉴于以上的问题,我们需要使用线程同步、和线程等待方法去解决这类问题的发生,具体操作如下:
在Food类中添加一个中间变量flag,此变量为Boolean类型,flag==true时表示可以生产;flag==false时候表示可以消费,并在set()和get()方法中记性判断,并在对应的条件下使用Object下的wait()方法使线程进入等待,以此可以解决菜品名和描述混淆的问题,对于进入等待后的线程需要等待其他线程执行完毕进行notify()手动唤醒后,方会启动,从而解决线程同步的问题,由此问题可解~~
修改后的代码如下:
Food类:设置中间变量flag,同时给set()和get()设置synchronized保证线程同步,并对两个方法进行判断是否需要进入线程等待,并在相应的执行操作后,进行线程唤醒(使用notify()方法);注:notify()是唤醒线程中的另一随机线程;notifyAll()是唤醒所有的线程。而本示例中只有两个线程所以使用notify()即可~
class Food {
private String Name;//菜品名称
private String Desc;//菜// 品描述
boolean flag = true;//true的时候表示可以生产,false时候可以消费
public String getName() {
return Name;
}
public void setName(String name) {
Name = name;
}
public String getDesc() {
return Desc;
}
public void setDesc(String desc) {
Desc = desc;
}
@Override
public String toString() {
return "Food{" +
"Name='" + Name + '\'' +
", Desc='" + Desc + '\'' +
'}';
}
public Food() {
}
public Food(String name, String desc) {
Name = name;
Desc = desc;
}
/*
* 生产食物set
* */
public synchronized void set(String Name, String Desc) {//同步
if (!flag){
try {
this.wait();//线程进入等待,让出cpu的时间片。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setName(Name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(Desc);
flag=false;//可以消费
notify();//唤醒其他任一个线程、
}
/*
* 消费食物get
* */
public synchronized void get() {//同步
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + "-->" + this.getDesc());
flag =true;
notify();//唤醒另一个线程
}
}
修改代码执行后执行结果~~
面试题:sleep()和wait() 的区别:
sleep():让当前线程进入休眠状态,同时让出cpu时间片,但不释放对象监视器的所有权;
wait():让当前线程进入休眠状态,同时让出cpu时间片,释放对象监视器的所有权,等待其他线程通过notify()方法或notifyAll()方法进行唤醒。