生产者有生产任务,消费者有消费任务,生产和消费可以同时进行,生产和消费的都是同一产品。
1、搭建示例
实现多线程同时生产并消费产品
1.创建一个类用于存放产品的信息;
2.类中包括生产产品和消费产品的功能;
3.创建一个类用于存放生产任务;
4.创建一个类用于存放消费任务;
//描述产品
class Product {
private String name;
private int count;
private boolean flag;
// 生产产品的功能
public synchronized void produce(String name) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "个" + name;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
count++;
flag = true;
notify();
}
// 消费产品的功能
public synchronized void consume() {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
flag = false;
notify();
}
}
// 生产任务
class Producer implements Runnable {
private Product pro;
public Producer(Product pro) {
this.pro = pro;
}
public void run() {
while (true) {
pro.produce("笔记本");
}
}
}
// 消费任务
class Consumer implements Runnable {
private Product pro;
public Consumer(Product pro) {
this.pro = pro;
}
public void run() {
while (true) {
pro.consume();
}
}
}
public class Demo1 {
public static void main(String[] args) {
Product pro = new Product();
Producer producer = new Producer(pro);
Consumer consumer = new Consumer(pro);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
此时结果如下:
当只有一个生产线程以及只有一个消费线程时,线程安全,实现了预期的效果,但是当有多个生产线程或消费线程时,结果如何?
2、生产与消费的安全问题
为了实现更快的生产与消费,将生产线程与消费线程分别增加一个,代码如下:
public class Demo1 {
public static void main(String[] args) {
Product pro = new Product();
Producer producer = new Producer(pro);
Consumer consumer = new Consumer(pro);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
此时结果如下:
这就出现了安全问题,例如连续生产多个产品但未消费以及连续消费同一个产品的情况。出现错误的原因是线程被唤醒之后没有判断flag标记而直接向下执行任务代码,以这段动画为例:
为了便于阅读文字,动画切换间隔设置的比较长。
因为T1线程被唤醒前,flag标记已经被改为了true,假如T1线程被唤醒后能够判断flag的话,错误就可以避免,修改代码如下:
// 生产产品的功能
public synchronized void produce(String name) {
// 将if改为while
while (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "个" + name;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
count++;
flag = true;
notify();
}
// 消费产品的功能
public synchronized void consume() {
// 将if改为while
while (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
flag = false;
notify();
}
此时结果如下:
虽然此时可以让线程返回去判断flag标记,看似解决了问题,但实际运行时却出现了线程死锁。接续接着之前的动画,分析出现死锁的原因:
此时假设T0线程唤醒的不是T1线程,而是T2或T3线程,那么T2或T3线程判断flag标记后可正常执行消费产品,因此实际上要保证,生产线程要唤醒消费线程,消费线程要唤醒生产线程,然而notify()方法唤醒的是线程池中的任意线程,将notify()改为notifyAll()方法,虽然会牺牲程序的性能,但线程全部被唤醒,就不会出现死锁的现象了,改正所有错误的代码如下:
//描述产品
class Product {
private String name;
private int count;
private boolean flag;
// 生产产品的功能
public synchronized void produce(String name) {
while (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = count + "个" + name;
System.out.println(Thread.currentThread().getName() + "生产了" + this.name);
count++;
flag = true;
notifyAll();
}
// 消费产品的功能
public synchronized void consume() {
while (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费了" + this.name);
flag = false;
notifyAll();
}
}
// 生产任务
class Producer implements Runnable {
private Product pro;
public Producer(Product pro) {
this.pro = pro;
}
public void run() {
while (true) {
pro.produce("笔记本");
}
}
}
// 消费任务
class Consumer implements Runnable {
private Product pro;
public Consumer(Product pro) {
this.pro = pro;
}
public void run() {
while (true) {
pro.consume();
}
}
}
public class Demo1 {
public static void main(String[] args) {
Product pro = new Product();
Producer producer = new Producer(pro);
Consumer consumer = new Consumer(pro);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
注意:
随着JKD版本的更新,在1.5版本之后出现比synchronized更加强大的实现同步锁的方法,详情参考使用Lock接口与Condition接口实现生产者与消费者。
版权声明:欢迎转载,欢迎扩散,但转载时请标明作者以及原文出处,谢谢合作! ↓↓↓