java多线程_生产者-消费者模式和线程间的通信(wait、notify)

描述

hello,这里旨在向大家介绍生产者-消费者模式wait、notify的使用。整体的思路是:

1、探讨一下什么是生产者-消费者模式。
2、线程不安全的生产者-消费者模式的实现。
3、线程安全的生产者-消费者模式的实现。
4、wait()和notify()的原理和使用。
5、使用wait()和notify()对生产者-消费者模式进行优化。

生产者-消费者模式

首先,生产者-消费者问题,也称为有限缓冲问题,是一个多线程同步问题的经典案例。

生产者-消费者问题所涉及到的角色有:

1、缓冲区: 用来存放数据;
2、生产者:多个生产者同时向缓冲区写入数据;
3、消费者:多个消费者同时消费缓冲区的数据;

生产者-消费者问题最重要的是:

1、当缓冲区满了的时候,生产者不能再写入数据,当缓冲区为空的时候,消费者不能再消费数据;
2、要保证在生产者加入、消费者消耗的过程中,不会产生错误的数据和行为。

线程不安全的生产者-消费者模式的实现

首先我们来实现一个非线程安全版本的生产者-消费者模式。它主要包括:

NotSafeDataBuffer:非线程安全的数据缓冲区类
Producer: 生产者实例
Consumer: 消费者者实例
NotSafePetStore: 缓冲区实例、生产动作、消费动作实例。

上面我们将生产者和生产动作、消费者和消费动作进行解耦,使得生产者、消费者可以实现复用。代码如下:

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @desc 不安全的生产者、消费者
 * */
public class Demo001_a {

    public static void main(String[] args) {
        final int THREAD_TOTAL = 20; //总共的线程数
        ExecutorService threadPool =
                Executors.newFixedThreadPool(THREAD_TOTAL); // 线程池
        for(int i=0; i<5; i++){
            //生产者
            threadPool.submit(new Producer(NotSafePetStore.produceAction,500));
            //消费者
            threadPool.submit(new Producer(NotSafePetStore.consumerAction,1500));
        }
    }

    /**
     * @desc 非线程安全的数据缓冲区类
     * */
   static class NotSafeDataBuffer<T>{
        public static final int MAX_AMOUNT = 10;
        private List<T> dataList = new LinkedList<>();

        //保存数量
        private AtomicInteger amount = new AtomicInteger(0);

        //向数据区增加一个元素
        public void add(T element) throws Exception{
            if(amount.get()>MAX_AMOUNT){
                System.out.println("队列已经满了!!!");
                return;
            }
            dataList.add(element);
            System.out.println( "add  " + element);
            amount.incrementAndGet();
            //如果数据不一致,就抛出异常
            if(amount.get()!=dataList.size()){
                throw new Exception(amount + "!=" + dataList.size() + " add");
            }
        }

        //从数据读出一个元素
        public T fetch() throws Exception{
            if(amount.get()<=0){
                System.out.println("队列已经空了!!!");
                return null;
            }
            T element = dataList.remove(0);
            System.out.println( "remove  " + element);
            amount.decrementAndGet();
            //如果数据不一致,就抛出异常
            if(amount.get()!=dataList.size()){
                throw new Exception(amount + "!=" + dataList.size() + " fetch");
            }
            return element;
        }
    }

    /**
     * @desc 生产者实例
     * */
    static class Producer implements Runnable{
        //生产的时间间隔,200毫秒
        public static final int PRODUCER_GAP = 200;
        //总次数
        static final AtomicInteger TURN = new AtomicInteger(0);
        //生产者对象编号
        static final AtomicInteger PRODUCER_NO = new AtomicInteger(1);
        String name = null; //生产者名称
        Callable action = null; //生产动作
        int gap = PRODUCER_GAP;
        public Producer(Callable action, int gap){
            this.action = action;
            this.gap = gap;
            name = "生产者-" + PRODUCER_NO.incrementAndGet();
        }

        @Override
        public void run() {
            while(true){

                try {
                    //执行生产动作
                    Object out = action.call();
                    //输出生产结果
                    if(null != out){
                        System.out.println(name + ":第" + TURN.get() + "轮生产:" + out);
                    }
                    //每一轮生产之后,稍微等待一下
                    Thread.sleep(gap);
                     //增加生产轮次
                    TURN.incrementAndGet();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * @desc 消费者者实例
     * */
    static class Consumer implements Runnable{
        //消费的时间间隔,100毫秒
        public static final int CONSUMER_GAP = 100;
        //总次数
        static final AtomicInteger TURN = new AtomicInteger(0);
        //消费者对象编号
        static final AtomicInteger CONSUMER_NO = new AtomicInteger(1);
        String name = null; //消费者名称
        Callable action = null; //消费者动作
        int gap = CONSUMER_GAP;
        public Consumer(Callable action, int gap){
            this.action = action;
            this.gap = gap;
            name = "消费者-" + CONSUMER_NO.incrementAndGet();
        }

        @Override
        public void run() {
            while(true){

                try {
                    //增加消费次数
                    TURN.incrementAndGet();
                    //执行消费
                    Object out = action.call();
                    //输出生产结果
                    if(null != out){
                        System.out.println(name + "第" + TURN.get() + "轮消费:" + out);
                    }
                    //每一轮消费之后,稍微等待一下
                    Thread.sleep(gap);

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    static class NotSafePetStore{
        //缓冲区
        private static NotSafeDataBuffer<String>
                buffer =  new NotSafeDataBuffer();

        //生产者执行动作
        static  Callable<String> produceAction = () ->{
            String str = randomStr();
            try{
                buffer.add(str);
            }catch (Exception ex ){
                ex.printStackTrace();
            }
            return str;
        };

        //消费者执行动作
        static  Callable<String> consumerAction = () ->{
            String str = null;
            try{
                str = buffer.fetch();
            }catch (Exception ex ){
                ex.printStackTrace();
            }
            return str;
        };

        /**
         * 生成随机字符串
         * */
        public static String randomStr(){
            Random random = new Random();
            random.setSeed(1000L);
            return random.nextInt() + " - producer";
        }

    }
}

程序开始并发执行一段时间之后,就会出现下面的问题:

java.lang.Exception: 2!=1 add
	at com.java.basic.thread.demo004.Demo001_a$NotSafeDataBuffer.add(Demo001_a.java:49)
	at com.java.basic.thread.demo004.Demo001_a$NotSafePetStore.lambda$static$0(Demo001_a.java:162)
	at com.java.basic.thread.demo004.Demo001_a$Producer.run(Demo001_a.java:95)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

这是因为多线程并发操作amount、dataList两个成员操作时次序混乱导致的。

线程安全的生产者-消费者模式的实现

解决线程安全问题很简单,为临界区代码加上 synchronized关键字即可。修改的地方在于创建一个数据缓存区安全的类SafeDataBuffer。

 /**
     * @desc 线程安全的数据缓冲区类
     * */
   static class SafeDataBuffer<T>{
        public static final int MAX_AMOUNT = 10;
        private List<T> dataList = new LinkedList<>();

        //保存数量
        private AtomicInteger amount = new AtomicInteger(0);

        //向数据区增加一个元素
        public synchronized void add(T element) throws Exception{
            if(amount.get()>MAX_AMOUNT){
                System.out.println("队列已经满了!!!");
                return;
            }
            dataList.add(element);
            System.out.println( "add  " + element);
            amount.incrementAndGet();
            //如果数据不一致,就抛出异常
            if(amount.get()!=dataList.size()){
                throw new Exception(amount + "!=" + dataList.size() + " add");
            }
        }

        //从数据读出一个元素
        public synchronized T fetch() throws Exception{
            if(amount.get()<=0){
                System.out.println("队列已经空了!!!");
                return null;
            }
            T element = dataList.remove(0);
            System.out.println( "remove  " + element);
            amount.decrementAndGet();
            //如果数据不一致,就抛出异常
            if(amount.get()!=dataList.size()){
                throw new Exception(amount + "!=" + dataList.size() + " fetch");
            }
            return element;
        }
    }

其它的代码一行不动。我们发现现在多个生产者和多个消费者已经可以正常的生产消费了。但是上面的解决方式使用了对象锁作为同步锁,使得所有的生产者和消费者都抢占同一个同步锁,最终所有的生产、消费动作都被串行化。

wait()和notify()的原理和使用

java对象中的wait()、notify()两个方法就如同信号开关,用于等待方和通知方之间的交互。

wait():阻塞当前线程并等待被唤醒。
notify(): 唤醒使用wait()进入阻塞状态的线程。

wait()方法的核心原理大致如下:

1、当线程调用了某个锁对象的wait方法之后,JVM会将当前线程加入锁对象的监视器的WaitSet(等待集合),等待被其他线程唤醒,关于锁对象的监视器(Monitor),大家可自行查阅资料了解。

2、当前线程会释放锁对象监视器的Owner权利,让其它线程抢夺锁对象监视器。

3、当前线程状态变成WAITING。

notify 方法有两个版本:

1、void notify():唤醒锁对象的监视器的WaitSet中的第一条等待线程。

2、void notifyAll():唤醒锁对象的监视器的WaitSet中的所有线程。

当等待线程被唤醒之后,会从监视器的WaitSet移动到EntryList,线程变为BLOCKED状态,重新竞争锁对象。

wait、notify 案例演示如下:

import java.util.Scanner;

/**
 * @desc wait 和 notify原理
 * */
public class Demo001_c {

    public static void main(String[] args) {

        new Thread(new WaitTarget(),"waitThread").start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(new NotifyTarget(),"notifyThread").start();
    }

    static Object locko = new Object();

    //等待线程
    static class WaitTarget implements Runnable{
        @Override
        public void run() {
            synchronized (locko){ //加锁
                try {
                    System.out.println("开始等待...");
                    locko.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                System.out.println("收到通知,当前线程继续执行...");
            }
        }
    }

    //通知线程
    static class NotifyTarget implements Runnable{
        @Override
        public void run() {
            synchronized (locko){ //加锁
                //接收屏幕输入yes
                Scanner input = new Scanner(System.in);
                System.out.println("是否唤醒等待线程:");
                if(input.next().equals("yes")) {
                    locko.notifyAll();
                }
                System.out.println("通知线程发出通知...");
            }
        }
    }

}

在屏幕中输入"yes"之后,等待的线程被唤醒。

需要注意的是 wait、notify 必须在synchronized同步块的内部使用,因为它们是锁对象提供的方法。

使用wait()和notify()对生产者-消费者模式进行优化

我们在上文实现的生产者-消费者模式,当缓冲区写满之后,生产者线程会进入轮询状态;当缓冲区为空的时候,消费者会进入轮询状态,这种轮询是比较消耗cpu的性能的。比较好的方式是通过wait()让线程进入阻塞状态,然后等待notify()唤醒。

我们会在SafeDataBuffer中加入3把锁:
LOCK_BOJECT :保护临界区资源。保护dataList和amount的操作。
NOT_FULL : 用于缓冲区满了之后,使得生产者线程进入阻塞;当有消费者消费之后,通知生产者进行生产。
NOT_EMPTY: 用于缓冲区为空,使得消费者线程进入阻塞;当有生产者生产之后,通知阻塞的消费者进行消费。

代码大致如下:

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @desc wait notify 和生产者消费者模式
 * */
public class Demo001_d {

    public static void main(String[] args) {
        final int THREAD_TOTAL = 20; //总共的线程数
        ExecutorService threadPool =
                Executors.newFixedThreadPool(THREAD_TOTAL); // 线程池
        for(int i=0; i<5; i++){
            //消费者
            threadPool.submit(new Consumer(SafePetStore.consumerAction,1500));
            //生产者
            threadPool.submit(new Producer(SafePetStore.produceAction,500));

        }
    }

    /**
     * @desc 数据缓冲区类
     * */
    static class SafeDataBuffer<T>{
        public static final int MAX_AMOUNT = 10;   //数据缓冲区的最大长度
        private List<T> dataList = new LinkedList<>();

        private Integer amount = 0; //数据缓冲区长度

        private final Object LOCK_BOJECT = new Object(); //保护临界区资源
        //用于缓冲区满了之后,使得生产者线程进入阻塞;当有消费者消费之后,通知生产者进行生产。
        private final Object NOT_FULL = new Object();
        //用于缓冲区为空,使得消费者线程进入阻塞;当有生产者生产之后,通知阻塞的消费者进行消费。
        private final Object NOT_EMPTY = new Object();

        //向数据区增加一个元素
        public void add(T element) throws Exception{

            while(amount>MAX_AMOUNT)
            {
                synchronized (NOT_FULL){
                    System.out.println("队列已经满了!!!");
                    NOT_FULL.wait();
                }
            }

            synchronized (LOCK_BOJECT){
                dataList.add(element);
                amount++;
                System.out.println( "add  " + element);
            }

            synchronized (NOT_EMPTY){
                NOT_EMPTY.notify(); //发送未空通知
            }

        }

        //从数据读出一个元素
        public T fetch() throws Exception{

            while(amount<=0)
            {
                synchronized (NOT_EMPTY){
                    System.out.println("队列已经空了!!!");
                    NOT_EMPTY.wait();
                }
            }

            T element = null;
            synchronized (LOCK_BOJECT){
                element = dataList.remove(0);
                System.out.println( "remove  " + element);
                amount--;
            }
            synchronized (NOT_FULL){
                NOT_FULL.notify(); //发送未满通知
            }
            return element;

        }
    }

    /**
     * @desc 生产者实例
     * */
    static class Producer implements Runnable{
        //生产的时间间隔,200毫秒
        public static final int PRODUCER_GAP = 200;
        //总次数
        static final AtomicInteger TURN = new AtomicInteger(0);
        //生产者对象编号
        static final AtomicInteger PRODUCER_NO = new AtomicInteger(1);
        String name = null; //生产者名称
        Callable action = null; //生产动作
        int gap = PRODUCER_GAP;
        public Producer(Callable action, int gap){
            this.action = action;
            this.gap = gap;
            name = "生产者-" + PRODUCER_NO.incrementAndGet();
        }

        @Override
        public void run() {
            while(true){

                try {
                    //执行生产动作
                    Object out = action.call();
                    //输出生产结果
                    if(null != out){
                        System.out.println(name + "-第" + TURN.get() + "轮生产:" + out);
                    }
                    //每一轮生产之后,稍微等待一下
                    Thread.sleep(gap);
                    //增加生产轮次
                    TURN.incrementAndGet();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * @desc 消费者者实例
     * */
    static class Consumer implements Runnable{
        //消费的时间间隔,100毫秒
        public static final int CONSUMER_GAP = 100;
        //总次数
        static final AtomicInteger TURN = new AtomicInteger(0);
        //消费者对象编号
        static final AtomicInteger CONSUMER_NO = new AtomicInteger(1);
        String name = null; //消费者名称
        Callable action = null; //消费者动作
        int gap = CONSUMER_GAP;
        public Consumer(Callable action, int gap){
            this.action = action;
            this.gap = gap;
            name = "消费者-" + CONSUMER_NO.incrementAndGet();
        }

        @Override
        public void run() {
            while(true){

                try {
                    //增加消费次数
                    TURN.incrementAndGet();
                    //执行消费
                    Object out = action.call();
                    //输出生产结果
                    if(null != out){
                        System.out.println(name + "第" + TURN.get() + "轮消费:" + out);
                    }
                    //每一轮消费之后,稍微等待一下
                    Thread.sleep(gap);

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    static class SafePetStore{
        //缓冲区
        private static SafeDataBuffer<String>
                buffer =  new SafeDataBuffer();

        //生产者执行动作
        static  Callable<String> produceAction = () ->{
            String str = randomStr();
            try{
                buffer.add(str);
            }catch (Exception ex ){
                ex.printStackTrace();
            }
            return str;
        };

        //消费者执行动作
        static  Callable<String> consumerAction = () ->{
            String str = null;
            try{
                str = buffer.fetch();
            }catch (Exception ex ){
                ex.printStackTrace();
            }
            return str;
        };

        /**
         * 生成随机字符串
         * */
        public static String randomStr(){
            Random random = new Random();
            random.setSeed(1000L);
            return random.nextInt() + " - producer";
        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值