问题描述:假设仓库中只能存放一件产品,生产者将生产的产品放入仓库,然后消费者取出来使用。
分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖、互为条件。对于生产者,没有生产产品之前,要通知消费者等待。在生产了一个产品之后,停止生产,通知消费者来消费。对于消费者,在消费一个产品之后,通停止消费。并且要通知生产者去生产。
因为这里面涉及到生产者线程和消费者线程之间的通信,所以只有 synchronized 是不够的。
Java中提供了5个方法来解决线程之间的通信,其中常用的是wait() 和 notify()方法。
final void wait() 表示线程一直等待,直到其他线程通知;
void wait(long timeout) 线程等待指定毫秒级别的参数的时间
final void wait(long timeout, int nanos) 线程等待指定毫秒+微妙的时间
final void notify() 唤醒一个处于等待状态的线程
final void notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行。
注意:它们均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。
下面是源码部分的书写:
//我们定义一个产品类,并且其中提供了Product类的name和color属性的set和get方法。
//还有一个isProduct属性来代表当前仓库中是否有产品,其实就是一个互斥锁。
//同时,我们不再在生产者线程和消费者线程中写 生产产品的put()方法 和 消费产品的get()方法的详细内容,
//而是直接在Product类中提供,然后在生产者和消费者线程中去调用。
//最后,我们先将put() get()方法用 synchronized关键字 声明为 同步方法 。
//然后用互斥锁 isProduct ,并结合wait() notify()方法,实现互斥操作。
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:45
* @description: 产品类
*/
public class Product {
private String name; //名字 :玉米 馒头
private String color; //颜色 :黄色 白色
private boolean isProduct = false; //默认没有生产
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//生产商品
public synchronized void put(String name, String color) throws InterruptedException {
//1.如果有商品了,等待唤醒
if(isProduct)
wait();
//2.生产商品
this.setName(name);
//加入sleep之后,进一步观察是否出现 生产玉米白色 的错误现象。
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setColor(color);
System.out.println("生产者生产商品,并且放入仓库中." + name + "---" + color);
//3.修改isProduct的值
isProduct = true;
//4.唤醒消费者
notify();
}
//消费商品
public synchronized void get() throws InterruptedException {
//1.等待商品
if(!isProduct)
wait();
//2.消费商品
System.out.println("消费者从仓库取出商品." + this.getName() + "---" + this.getColor());
//3.修改isProduct的值
isProduct = false;
//4.唤醒生产者
notify();
}
}//class end
//这里是生产者线程,直接调用Product类中的put()方法。采用实现Runnable接口的方式来定义。
//其中,提供了两个构造函数,并且在run()方法的重写中,加入对i的判断,当i为偶数时,生产白色的馒头;i为奇数时,生产黄色的玉米。
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:46
* @description: 生产者类
*/
public class Producer implements Runnable{
private Product product;
public Producer(){
super();
}
public Producer(Product product){
super();
this.product = product;
}
//馒头(i%2 = 0)和玉米(i%2 = 1)交替生产
@Override
public void run() {
int i = 0;
while(true){
//进行生产
if(i%2 == 0){
try {
product.put("馒头","白色");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
try {
product.put("玉米","黄色");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//改变i的值
i++;
}
}
}//class end
//这里是消费者线程,直接调用Product类中的get()方法。采用实现Runnable接口的方式来定义。
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:46
* @description: 消费者线程
*/
public class Consumer implements Runnable {
private Product product;
public Consumer(){
super();
}
public Consumer(Product product) {
super();
this.product = product;
}
@Override
public void run() {
while(true){
try {
product.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}//class end
//这是我们的测试类
package java_demo.Thread.commu1;
/**
* @author: lei
* @data: 2019.10.14 16:42
* @description:
* 业务:
* 1、仓库中只放一件商品
* 2、生产者交替生产玉米和馒头
* 3、生产者和消费者交替工作
* 设计:
* 1、产品类 Product(name、color)
* 2、生产者线程 Producer
* 3、消费者线程 Consumer
* 4、测试类 Test
*
* 问题1:两个线程之间没有交互
* 问题2:和商品之间没有关系 解决:提供商品类、生产者交替生产馒头和玉米、消费者消费、测试类中给生产者和消费者传入同一个产品
* 问题3:出现白色玉米和黄色馒头的错误情况 解决:同步代码块
* 问题4:生产放到生产者线程中,消费放到消费者线程中,不符合面向对象的封装思想 解决:把生产和消费作为方法放到product类中
* 问题5:交替生产玉米和馒头 其实就是问题1,但最后才去解决:线程通信 借助方法:wait() notify() 注意只要方法声明中有synchronized 就可以直接调用这些方法
*
*/
public class Test {
public static void main(String[] args) {
//创建两个线程
//创建产品
Product p = new Product();
Thread th1 = new Thread(new Producer(p));
Thread th2 = new Thread(new Consumer(p));
//启动线程
th1.start();
th2.start();
}
}//class end
测试结果如下: