Java多线程之并发协作生产者消费者设计模式
时间:2016-11-03
本文章向大家介绍Java多线程之并发协作生产者消费者设计模式,文章涉及到ava多线程、生产者消费者等相关知识,需要学习的朋友可以参考一下本文章。
两个线程一个生产者个一个消费者
需求情景
- 两个线程,一个负责生产,一个负责消费,生产者生产一个,消费者消费一个
涉及问题
- 同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加锁机制
- wait() / nofity() 方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
- wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。
- notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
代码实现(共三个类和一个main方法的测试类)
Resource.java
/**
* Created by yuandl on 2016-10-11./**
* 资源
*/
public class Resource {
/*资源序号*/
private int number = 0;
/*资源标记*/
private boolean flag = false;
/**
* 生产资源
*/
public synchronized void create() {
if (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;
try {
wait();//让生产线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;//生产一个
System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
flag = true;//将资源标记为已经生产
notify();//唤醒在等待操作资源的线程(队列)
}
/**
* 消费资源
*/
public synchronized void destroy() {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费者****" + number);
flag = false;
notify();
}
}
Producer.java
/**
* Created by yuandl on 2016-10-11.
*
/**
* 生产者
*/
public class Producer implements Runnable {
private Resource resource;
public Producer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.create();
}
}
}
Consumer.java
/**
* 消费者
*/
public class Consumer implements Runnable {
private Resource resource;
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.destroy();
}
}
}
ProducerConsumerTest.java
/**
* Created by yuandl on 2016-10-11.
*/
public class ProducerConsumerTest {
public static void main(String args[]) {
Resource resource = new Resource();
new Thread(new Producer(resource)).start();//生产者线程
new Thread(new Consumer(resource)).start();//消费者线程
}
}
打印结果:
Thread-0生产者------------1
Thread-1消费者****1
Thread-0生产者------------2
Thread-1消费者****2
Thread-0生产者------------3
Thread-1消费者****3
Thread-0生产者------------4
Thread-1消费者****4
Thread-0生产者------------5
Thread-1消费者****5
Thread-0生产者------------6
Thread-1消费者****6
Thread-0生产者------------7
Thread-1消费者****7
Thread-0生产者------------8
Thread-1消费者****8
Thread-0生产者------------9
Thread-1消费者****9
Thread-0生产者------------10
Thread-1消费者****10
以上打印结果可以看出没有任何问题
多个线程,多个生产者和多个消费者的问题
需求情景
- 四个线程,两个个负责生产,两个个负责消费,生产者生产一个,消费者消费一个
涉及问题
- notifyAll()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的所有线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
再次测试代码
ProducerConsumerTest.java
**
* Created by yuandl on 2016-10-11.
*/
public class ProducerConsumerTest {
public static void main(String args[]) {
Resource resource = new Resource();
new Thread(new Consumer(resource)).start();//生产者线程
new Thread(new Consumer(resource)).start();//生产者线程
new Thread(new Producer(resource)).start();//消费者线程
new Thread(new Producer(resource)).start();//消费者线程
}
}
运行结果:
Thread-0生产者------------100
Thread-3消费者****100
Thread-0生产者------------101
Thread-3消费者****101
Thread-2消费者****101
Thread-1生产者------------102
Thread-3消费者****102
Thread-0生产者------------103
Thread-2消费者****103
Thread-1生产者------------104
Thread-3消费者****104
Thread-1生产者------------105
Thread-0生产者------------106
Thread-2消费者****106
Thread-1生产者------------107
Thread-3消费者****107
Thread-0生产者------------108
Thread-2消费者****108
Thread-0生产者------------109
Thread-2消费者****109
Thread-1生产者------------110
Thread-3消费者****110
通过以上打印结果发现问题
- 101生产了一次,消费了两次
- 105生产了,而没有消费
原因分析
- 当两个线程同时操作生产者生产或者消费者消费时,如果有生产者或者的两个线程都wait()时,再次notify(),由于其中一个线程已经改变了标记而另外一个线程再次往下直接执行的时候没有判断标记而导致的。
- if判断标记,只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。
解决方案
- while判断标记,解决了线程获取执行权后,是否要运行!也就是每次wait()后再notify()时先再次判断标记
代码改进(Resource中的if->while)
Resource.java
/**
* Created by yuandl on 2016-10-11./**
* 资源
*/
public class Resource {
/*资源序号*/
private int number = 0;
/*资源标记*/
private boolean flag = false;
/**
* 生产资源
*/
public synchronized void create() {
while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;
try {
wait();//让生产线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;//生产一个
System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
flag = true;//将资源标记为已经生产
notify();//唤醒在等待操作资源的线程(队列)
}
/**
* 消费资源
*/
public synchronized void destroy() {
while (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费者****" + number);
flag = false;
notify();
}
}
再次发现问题
- 打印到某个值比如生产完74,程序运行卡死了,好像锁死了一样。
原因分析
- notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致”死锁”。
解决方案
- notifyAll解决了本方线程一定会唤醒对方线程的问题。
最后代码改进(Resource中的notify()->notifyAll())
Resource.java
/**
* Created by yuandl on 2016-10-11./**
* 资源
*/
public class Resource {
/*资源序号*/
private int number = 0;
/*资源标记*/
private boolean flag = false;
/**
* 生产资源
*/
public synchronized void create() {
while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;
try {
wait();//让生产线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;//生产一个
System.out.println(Thread.currentThread().getName() + "生产者------------" + number);
flag = true;//将资源标记为已经生产
notifyAll();//唤醒在等待操作资源的线程(队列)
}
/**
* 消费资源
*/
public synchronized void destroy() {
while (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费者****" + number);
flag = false;
notifyAll();
}
}
运行结果:
Thread-0生产者------------412
Thread-2消费者****412
Thread-0生产者------------413
Thread-3消费者****413
Thread-1生产者------------414
Thread-2消费者****414
Thread-1生产者------------415
Thread-2消费者****415
Thread-0生产者------------416
Thread-3消费者****416
Thread-1生产者------------417
Thread-3消费者****417
Thread-0生产者------------418
Thread-2消费者****418
Thread-0生产者------------419
Thread-3消费者****419
Thread-1生产者------------420
Thread-2消费者****420
以上就大功告成了,没有任何问题
- Vijos P1113 不高兴的津津【模拟】
- Linux下MySQL的彻底卸载和安装配置字符集
- Codeforces 626F Group Projects(滚动数组+差分dp)
- Vijos P1103 校门外的树【线段树,模拟】
- BZOJ 1061: [Noi2008]志愿者招募【单纯形裸题】
- SQL vs NoSQL:如何选择?
- 线性规划之单纯形法【超详解+图解】
- NodeJS 应用仓库钓鱼
- Codeforces 626D Jerry's Protest(暴力枚举+概率)
- CodeM美团点评编程大赛初赛B轮 黑白树【DFS深搜+暴力】
- Uva 10339 - Watching Watches【数论,暴力】
- Codeforces 626E Simple Skewness(暴力枚举+二分)
- 如何启用Windows 10客户端Hyper-V
- 51Nod 1632 B君的连通(递归,快速幂)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- linux ssh端口转发的三种方式
- 学习Centos7软raid5的挂载
- linux中crw brw lrw等等文件属性是什么
- centos中文件与权限的基本操作教程
- 在Linux中使用tcpdump命令捕获与分析数据包详解
- easyswoole一键安装脚本及宝塔安装错误问题
- CentOS7系统增加swap的操作方法实例
- iOS逆向之OpenSSH登录iPhone
- linux中SUID,SGID与SBIT的奇妙用途详解
- 详解Linux文件操作知识点
- Linux中nohup与&的用法和区别详解
- Linux中有效地管理进程的8个命令
- Centos7 下安装python3及卸载的教程
- Linux使用VIM编辑器的方法
- Centos安装MYSQL8.X的教程