两个线程一个生产者个一个消费者

需求情景

  • 两个线程,一个负责生产,一个负责消费,生产者生产一个,消费者消费一个

涉及问题

  • 同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加锁机制
  • 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.
*
/**
* 生产者 http://www.manongjc.com
*/ 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

以上就大功告成了,没有任何问题

原文地址:http://www.manongjc.com/article/1583.html

Java多线程之并发协作生产者消费者设计模式的更多相关文章

  1. Java多线程-并发协作(生产者消费者模型)

    对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓 ...

  2. Java 多线程学习笔记:生产者消费者问题

    前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后 ...

  3. Java多线程—阻塞队列和生产者-消费者模式

    阻塞队列支持生产者-消费者这种设计模式.该模式将“找出需要完成的工作”与“执行工作”这两个过程分离开来,并把工作项放入一个“待完成“列表中以便在随后处理,而不是找出后立即处理.生产者-消费者模式能简化 ...

  4. Java 多线程 (并发)总结

    一.概念 1. 维基百科解释 进程是什么? http://zh.wikipedia.org/wiki/%E8%BF%9B%E7%A8%8B 线程是什么? http://zh.wikipedia.org ...

  5. Java多线程与并发基础

    CS-LogN思维导图:记录专业基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN 多线程与并发基础 实现多线程 面试题1:有几种实现线程的方法,分别是什么 ...

  6. JAVA多线程和并发基础面试问答(转载)

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  7. [转] JAVA多线程和并发基础面试问答

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  8. JAVA多线程和并发基础面试问答

    转载: JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对 ...

  9. 【多线程】JAVA多线程和并发基础面试问答(转载)

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

随机推荐

  1. Play jQuery with Node.js

    Motivation Move the fucking browser interactions out of javascript development cycle, since Chrome i ...

  2. iOS 统计App 的代码总行数

    打开Terminal,cd 到项目的根目录下,输入以下命令 find . -name "*.m" -or -name "*.mm" -or -name &quo ...

  3. JQuery EasyUI DataGrid常用操作及注意事项(未完)

    1.获取当前选中行,如果没有选中行,则返回 null var row = $('#gridID').datagrid('getSelected'); 2.获取当前所有选中行数据,返回的是选择的数组数据 ...

  4. SQL Server 2008 R2 未能加载文件或程序集Microsoft.SqlServer.Sqm...

    错误提示:未能加载文件或程序集“Microsoft.SqlServer.Sqm, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8 ...

  5. 最小生成树——kruskal算法

    kruskal和prim都是解决最小生成树问题,都是选取最小边,但kruskal是通过对所有边按从小到大的顺序排过一次序之后,配合并查集实现的.我们取出一条边,判断如果它的始点和终点属于同一棵树,那么 ...

  6. 关于for、foreach、filter等的一些用法

    通常我们使用得最熟悉的是for循环. 比如对于一组数字的排大小,可以使用冒泡法. var a=[];     for(var d=0;d<5;d++){         var b=window ...

  7. Android Saving Data(二)

    Saving File android读写文件的形式和普通的java IO的方式并没有什么不同,唯一有所限制的是当我们创建文件的时候不能够在像javaSE那样随意了.一般android读写文件有两种形 ...

  8. “System.Data.SqlClient.SqlConnection”的类型初始值设定项引发异常---解决方案

    "System.Data.SqlClient.SqlConnection"的类型初始值设定项引发异常 问题出在了 .net 的C:\WINDOWS\Microsoft.NET\Fr ...

  9. HttpServletRequest常用获取URL的方法

    1.request.getRequestURL() 返回的是完整的url,包括Http协议,端口号,servlet名字和映射路径,但它不包含请求参数.2.request.getRequestURI() ...

  10. centos6.3环境下升级python及MySQLdb的安装

    近来突然想鼓捣下linux下的python,看下Python数据库方面的东西,想着在centos下测试下.然而安装的过程有很多坑.下面对整个流程进行下记录 1.python基本库的安装 在安装pyth ...