【多线程】生产者消费者传统版和阻塞队列版

前言

等待与唤醒机制其实就是经典的生产者与消费者问题

我们知道,生产者要想和消费者作用在一件产品上,必须要用到锁机制。通过锁机制保证一个产品同一时间只有一个角色在执行。

下面将分别使用Synchronized和Lock锁解决生产者和消费者的问题。

1Synchronized版
/**
 * 线程之间的通信问题:生产者与消费者问题!
 * A和B操作同一个变量,线程交替执行,num=0
 * A num+1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
 
//判断等待、业务、通知
class Data{  //数字资源类
    private int number = 0;
 
    //+1
    public synchronized void increment() throws InterruptedException {
        if (number!=0){
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,+1完毕了
        this.notifyAll();
    }
 
    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            //进入等待状态
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程,-1完毕了
        this.notifyAll();
    }
 
}

可以看到线程有序执行没有问题。

在这里插入图片描述

2、虚假唤醒

当我们把线程数增加到4个时,2个增操作、2个减操作,代码会报错。
在这里插入图片描述

原因是你的增减方法中存在if条件判断,当使用if判断时,只要有一个条件满足,所有被满足的线程都会被唤醒,也就是说一次会唤醒2个+或2个-。解决此问题的办法就是把if判断换成while循环。参考jdk手册
在这里插入图片描述

3、Lock版

Lock锁是JUC提供的一个接口。我们在使用时一般用它的实现类ReetrantLock(可重入锁)

在synchronized中我们使用wait和notify进行等待唤醒操作,wait、notify存在于Object类中。而Lock提供的await、signal属于JUC包,Juc为我们提供了condition.await和condition.signal来进行等待与唤醒操作~
在这里插入图片描述

/**
 * 线程之间的通信问题:生产者与消费者问题!
 * A和B操作同一个变量,线程交替执行,num=0
 * A num+1
 * B num-1
 */
public class B {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data2.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data2.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data2.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                try {
                    data2.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
 
//判断等待、业务、通知
class Data2{  //数字资源类
    private int number = 0;
 
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
 
    //condition.await();等待
    //condition.signalAll();唤醒
    //+1
    public  void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0){
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程,+1完毕了
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
 
    //-1
    public  void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0){
                //进入等待状态
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程,-1完毕了
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
 
}

在这里插入图片描述

4、Condition精准通知与唤醒线程

使用Lock锁之后,我们同样实现了等待与唤醒机制,但是ABCD执行是没有顺序的,如何让ABCD顺序执行呢?Condition~

其实原理非常简单,我们设置线程休眠是按顺序唤醒即可A==>B==>C

/**
 * A执行完调用B,B执行完调用C,C执行完调用A
 */
public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printA();
            }
        },"A").start();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printB();
            }
        },"B").start();
 
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data3.printC();
            }
        },"C").start();
    }
}
 
class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1;         //1是A,2是B,3是C
 
    public void printA(){
        lock.lock();
        try {
            //判断、执行、通知
            while (number !=1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"==>AAAA");
            number = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
 
    public void printB(){
        lock.lock();
        try {
            //判断、执行、通知
            while (number !=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"==>BBBB");
            number = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
 
    public void printC(){
        lock.lock();
        try {
            //判断、执行、通知
            while (number !=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"==>CCCC");
            number = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

5、阻塞队列版

无论是lock还是sychronized都需要我们手动控制等待和唤醒,使用BlockingQueue版解决生产者消费者问题可以达到自动控制等待、唤醒与停止。

class MyResource {
    private volatile boolean FLAG = true; //默认开启,进行生产+消费
    private AtomicInteger atomicInteger = new AtomicInteger();

    BlockingQueue<String> blockingQueue = null;

    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProdu() throws Exception {
        String data = null;
        boolean retValue;
        while (FLAG) {
            data = atomicInteger.incrementAndGet() + "";
            retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if (retValue) {
                System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "成功");
            } else {
                System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "失败");
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "\t 大老板叫停了,表示FLAG=false,生产动作结束");
    }

    public void myConsumer() throws Exception {
        String result = null;
        while (FLAG) {
            result = blockingQueue.poll(2, TimeUnit.SECONDS);
            if (null == result || result.equalsIgnoreCase("")) {
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t 超过2秒钟没有取到蛋糕,消费退出");
                System.out.println();
                System.out.println();
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t 消费队列蛋糕" + result + "成功");
        }
    }

    public void stop()throws Exception{
        this.FLAG = false;
    }
}


public class ProdConsumer_BlockQueueDemo {
    public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
            try {
                myResource.myProdu();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Prod").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
            System.out.println();
            System.out.println();
            try {
                myResource.myConsumer();
                System.out.println();
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Consumer").start();

        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("5秒钟时间到,大老板main线程叫停,活动结束");

        myResource.stop();
    }
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值