分享

Java多线程之并发协作生产者消费者设计模式

 孤独一兵 2016-11-01
  • 两个线程一个生产者个一个消费者

    • 需求情景

    • 涉及问题

    • 代码实现(共三个类和一个main方法的测试类)

  • 多个线程,多个生产者和多个消费者的问题

    • 需求情景

    • 涉及问题

    • 再次测试代码

    • 代码改进(Resource中的if->while)

    • 最后代码改进(Resource中的notify()->notifyAll())

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

需求情景

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

涉及问题

  • 同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加锁机制

  • 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();

}

}

运行结果

Java多线程之并发协作生产者消费者设计模式

  • 再次发现问题

    • 打印到某个值比如生产完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://blog.csdn.net/linglongxin24/article/details/52788774

在学习过程中如果有任何的问题,请来极乐网提问!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多