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

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

需求情景

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

涉及问题

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

时间: 2024-10-12 14:41:56

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

Java线程:并发协作-生产者消费者模型

对于多线程程序来说,不管任何编程语言,生产者消费者模型都是最经典的. 实际上,准确的说应该是"生产者-消费者-仓储"模型,离开了仓储,生产者消费者模型就显得没有说服力了. 对于此模型,应该明确以下几点: 生产者仅仅在仓储未满时候生产,仓满则停止生产. 消费者仅仅在仓储有产品时候才能消费,仓空则等待. 当消费者发现仓储没有产品的时候会通知生产者生产. 生产者在生产出可消费产品时候,应该通知消费者去消费. 此模型将要结合java.lang.Object的wait与notify,notify

11.9-全栈Java笔记: 线程并发协作(生产者/消费者模式)

多线程环境下,我们经常需要多个线程的并发和协作.这个时候,就需要了解一个重要的多线程并发协作模型"生产者消费者模式". 什么是生产者? 生产者指的是负责生产数据的模块(这里模块可能是:方法.对象.线程.进程). 什么是消费者? 消费者指的是负责处理数据的模块(这里模块可能是:方法.对象.线程.进程). 什么是缓冲区? 消费者不能直接使用生产者的数据,它们之间有个"缓冲区".生产者将生产好的数据放入"缓冲区",消费者从"缓冲区"

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

前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后,自行实现阻塞队列. (一)准备 在多线程中,生产者-消费者问题是一个经典的多线程同步问题.简单来说就是有两种线程(在这里也可以做进程理解)——生产者和消费者,他们共享一个固定大小的缓存区(如一个队列).生产者负责产生放入新数据,消费者负责取出缓存区的数据.具体介绍请参考 Producer-consumer

java中多线程通信实例:生产者消费者模式

线程间的通信: 其实就是多个线程再操作同一个资源,但是操作的动作不同   当某个线程进入synchronized块后,共享数据的状态不一定满足该线程的需要,需要其他线程改变共享数据的状态后才能运行,而由于当时线程对共享资源时独占的,它必须解除对共享资源的锁定的状态,通知其他线程可以使用该共享资源. Java中的 wait(),notify(),notifyAll()可以实现线程间的通信. 生产者--消费者问题是典型的线程同步和通信问题 /** * 生产者和消费者问题,生产者生成出产品,消费者去购

JAVA多线程和并发基础面试问答【转】

JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单 一进程.线程可以被称为轻量

17、JAVA多线程和并发基础面试问答

JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(se

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

JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(se

Java多线程视频教程并发编程面试知识

课程目录:  1-1.并发编程入门到实战课程简介1-2.什么是并发编程1-3.并发编程的挑战之频繁的上下文切换1-4.并发编程的挑战之死锁1-5.并发编程的挑战之线程安全1-6.并发编程的挑战之资源限制2-1.进程与线程的区别2-2.线程的状态及其相互转换2-3.创建线程的方式(上)2-4.创建线程的方式(下)2-5.线程的挂起及其恢复2-6.线程的中断操作2-7.线程的优先级2-8.守护线程3-1.什么是线程安全性3-2.从字节码角度剖析线程不安全操作3-3.原子性操作3-4.深入理解sync

最常见的15个Java多线程,并发面试问题

例如,用于DMA交易的高容量和低延迟电子交易系统通常是并发的.大多数情况下,他们专注于微秒延迟,这就是为什么拥有如何有效地最小化延迟和提高吞吐量知识是如此重要. 这些是我最喜欢的关于Java的线程面试问题.我没有提供这些线程访谈问题的答案,但我会尽可能给你一个提示.我会更新帖子就详细的答案,就像我最近在Java中发布的10个Singleton面试问题一样. 15 Java Thread Interview Questions and answers 无论如何,这里是一些常见的Java多线程和并发