iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >Java多线程案例之阻塞队列
  • 393
分享到

Java多线程案例之阻塞队列

java面试阻塞队列生产者消费者模型多线程 2023-09-01 21:09:47 393人浏览 八月长安
摘要

文章目录 一. 认识阻塞队列1. 什么是阻塞队列2. 生产者消费者模型3. 标准库中阻塞队列类 二. 基于循环队列实现的简单阻塞队列1. 循环队列的简单实现2. 阻塞队列的简单实现

文章目录

一. 认识阻塞队列

1. 什么是阻塞队列

阻塞队列本质上还是一种队列, 和普通队列一样, 遵循先进先出, 后进后出的规则, 但阻塞队例相比于普通队列的特殊之处在于阻塞队列的阻塞功能, 主要基于多线程使用.

  1. 如果队列为空, 执行出队列操作, 就会使线程陷入阻塞, 阻塞到另一个线程往队列里添加元素(队列不空)为止.
  2. 如果队列满了,执行入队列操作, 也会使线程阻塞, 阻塞到另一个线程从队列取走元素位置(队列不满)为止.

2. 生产者消费者模型

基于阻塞队列的阻塞特性是可以实现 “生产者消费者模型” 的, 那么什么是生产者消费者模型呢?

还是用生活中的例子来解释, 这不还有半个月就要过年了, 大年当晚我们会吃年夜饭, 饺子就是年夜饭当中的一份主食, 那么要想吃到饺子, 最好就是一家人在一起把饺子包好, 简单来讲包饺子的步骤有: 擀饺子皮+包饺子.

有下面两种包饺子方式 :

  1. 每个人, 都分别进行 擀饺子皮+包饺子这样的操作, 但毕竟家里面的擀面杖不会准备那么多吧, 大家会竞争面杖, 一个人使用擀面杖的使用, 其他人就得阻塞等待, 这就影响了包饺子的效率了.
  2. 一个人专门负责擀饺子皮, 另外三个人负责包, 擀饺子的人每次擀好一个皮, 就放到 盖帘 上, 其他人每次都从盖帘上取一个皮包饺子.

其实第二种包饺子方式就是生产者消费者模型的运用, 擀饺子皮的那个人就是生产者, 其他负责包饺子的人就是消费者, 放饺子皮的盖帘就相当于阻塞队列, 如果擀饺子皮的人擀的太慢生产的饺子皮供不上使用, 不一会盖帘上没皮了, 包饺子的人就得等一会儿再包; 如果擀饺子的人擀的太快了, 包的速度跟不上擀的速度, 盖帘上放满了饺子皮, 擀饺子皮的人就得等一会儿再擀.

img

生产者消费者模型能够给程序带来两个非常重要的好处, 一是可以实现实现了发送方和接收方之间的 “解耦” , 二是可以 “削峰填谷” , 保证系统的稳定性, 具体理解如下:

服务器相互调用的场景中假设有两个服务器A(请求服务器), B(应用服务器), A把请求转发给B处理, B处理完了把结果反馈给A, 这种情况下, A和B之间的耦合是比较高的, A要调用B, A 务必要知道B的存在, 如果B挂了, 很容易引起A的bug, 再比如再加一个C服务器, 此时也需要对A修改不少代码, 就需要针对A重新修改代码, 重新测试, 重新发布, 重新部署等, 这就非常麻烦了.

img

而针对上述场景, 使用生产者消费者模型就可以有效的降低耦合,

img
A和B之间通过一个阻塞队列来通信, 此时A是不知道B的, A只知道队列, 也就是说A的代码中没有任何一行代码和B相关; 同样的, B也是不知道A的, B也是只知道队列, B的代码中,也没有任何一行代码和A相关.

如果B挂了, 对于A没有任何影响, 因为队列还是正常的, A仍然可以给队列插入元素, 如果队列满就先阻塞等待; 同样如果A挂了, 也对于B没啥影响, 队列是正常的B就仍然可以从队列取元素, 如果队列空了, 也就阻塞等待就好了; 也就是说, AB任何一方挂了不会对对方造成影响, 同时, 新增一个C来作为消费者, 对于A来说仍然是无感知的.

削峰填谷” 可以联想三峡大坝的水库, 三峡大坝的水库,

img

如果上游水多了, 三峡大坝就会关闸蓄水, 此时就相当于由三峡大坝承担了上游的冲击, 对下游起到了很好的保护左右, 这就是 “削峰” 作用; 如果上游水少了, 三峡大坝开闸放水, 有效保证下游的用水情况, 避免出现干旱灾害, 这就是 “填谷” 作用.

而在服务器开发中, 上游就是用户发送的请求, 下游就是一些执行具体业务的服务器, 用户发多少请求这是不可控的, 有的时候请求多, 有的时候请求少, 而如果没有使用生产者消费者模型, A服务器用户请求暴涨, 此时如果没有充分的准备, B服务器来不及响应一下没抗住, 就可能会挂掉.

但是, 如果使用生产者消费者模型, 那么即使A服务器请求暴涨, 也不会影响到B, 这是因为A请求暴涨后, 用户的请求都被打包到阻塞队列中(如果阻塞队列有界, 则会引起队列阻塞, 不会影响到B), B可以从阻塞队列中取出元素以合适的速度来处理这些请求, 这就是 “削峰填谷” 的作用了.

img

3. 标准库中阻塞队列类

Java标准库也提供了阻塞队列的标准类, 常用的有下面几个:

  • ArrayBlockingQueue : 基于数组实现界阻塞队列
  • LinkedBlockingQueue : 基于链表实现的有界阻塞队列
  • PriorityBlockingQueue : 带有优先级(堆)的无界阻塞队列
  • BlockingQueue接口 : 上面的类实现了该接口

阻塞队列类的核心方法:

方法解释
void put(E e) throws InterruptedException带有阻塞特性的入队操作方法
E take() throws InterruptedException带有阻塞特性的出队操作方法
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException带有阻塞特性的入队操作方法, 并且可以设置最长等待时间
E poll(long timeout, TimeUnit unit) throws InterruptedException带有阻塞特性的出队操作方法, 并且可以设置最长等待时间

注意 : 其他一些重载的offer, poll等普通队列方法也是支持使用的, 但是这些方法就不带有阻塞功能了.

代码示例:

下面的代码是基于标准库的阻塞队列简单实现的生产者消费者模型.

public class TestDemo19 {    public static void main(String[] args) {        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();        //消费者线程        Thread customer = new Thread(() -> {           while (true) {               try {                   Integer result = blockingQueue.take();                   System.out.println("消费元素: " + result);               } catch (InterruptedException e) {                   throw new RuntimeException(e);               }           }        });        customer.start();        //生产者线程        Thread producer = new Thread(() -> {            int count = 0;            while (true) {                try {                    blockingQueue.put(count);                    System.out.println("生产元素: " + count);                    count++;                    Thread.sleep(500);                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }            }        });        producer.start();    }}

执行结果:

img

二. 基于循环队列实现的简单阻塞队列

1. 循环队列的简单实现

要实现一个阻塞队列, 需要先实现一个普通的循环队列, 循环队列是基于数组实现的, 这里最重要的是如何将队列为空状态与满状态区分开来, 对于这里的实现不懂得可以看看看我前面博客中关于循环队列的内容, 这里就是不做过多的赘述了 : 队列与集合Queue,Deque的理解和使用 , 用栈实现队列,用队列实现栈,设计循环队列 , 阻塞队列最核心的就是出队和入队操作, 所以我们这里重点实现这两个方法, 代码如下:

//普通的循环队列class MyBlockingQueue {    //存放元素的数数组    private int[] items = new int[1000];    //队头指针    private int head = 0;    //队尾指针    private int tail = 0;    //记录队列元素的个数    private int size = 0;    //入队操作    public void put (int val) {        if (size == items.length) {            //队列满了            return;        }        items[tail++] = val;        //等价于 tail %= items.length        if (tail >= items.length) {            tail = 0;        }        size++;    }    //出队操作    public Integer take() {        int resulet = 0;        if (size == 0) {            //队列空了            return null;        }        resulet = items[head++];        //等价于 head %= elem.length        if (head >= items.length) {            head = 0;        }        size--;        return resulet;    }}

2. 阻塞队列的简单实现

首先要实现的阻塞队列在大多数情况下是在多线程情况下使用的, 所以要考虑线程安全问题, 上面循环队列的代码takeput方法都有写操作, 直接加即可.

//线程安全的循环队列class MyBlockingQueue {    //存放元素的数数组    private int[] items = new int[1000];    //队头指针    private int head = 0;    //队尾指针    private int tail = 0;    //记录队列元素的个数    private int size = 0;    //入队操作    public void put (int val) {        synchronized (this) {            if (size == items.length) {                //队列满了                return;            }            items[tail++] = val;            //等价于 tail %= items.length            if (tail >= items.length) {                tail = 0;            }            size++;        }    }    //出队操作    public Integer take() {        int resulet = 0;        synchronized (this) {            if (size == 0) {                //队列空了                return null;            }            resulet = items[head++];            //等价于 head %= elem.length            if (head >= items.length) {                head = 0;            }            size--;            return resulet;        }    }}

然后就是实现阻塞效果了, 主要是使用waitnotify实现线程的阻塞等待.

入队时, 队列满了需要使用wait方法使线程阻塞, 直到有元素出队队列不满了再使用notify通知线程执行.

出队时, 队列为空也需要使用wait方法使线程阻塞, 直到有新元素入队再使用notify通知线程执行.

代码如下:

class MyBlockingQueue {    //存放元素的数数组    private int[] items = new int[1000];    //队头指针    private int head = 0;    //队尾指针    private int tail = 0;    //记录队列元素的个数    private int size = 0;    //入队操作    public void put (int val) throws InterruptedException {        synchronized (this) {            if (size == items.length) {                //队列满了,阻塞等待                this.wait();            }            items[tail++] = val;            //等价于 tail %= items.length            if (tail >= items.length) {                tail = 0;            }            size++;            //唤醒因队列空造成的阻塞wait            this.notify();        }    }    //出队操作    public Integer take() throws InterruptedException {        int resulet = 0;        synchronized (this) {            if (size == 0) {                //队列空了,阻塞等待                this.wait();            }            resulet = items[head++];            //等价于 head %= elem.length            if (head >= items.length) {                head = 0;            }            size--;            //唤醒因队列满造成的阻塞wait            this.notify();            return resulet;        }    }}

上述代码已经基本实现了阻塞队列的功能, 但不够完善, 这里的代码再改良一下把判断队列满或者空的wait部分的代码, 把if改成while是更好的, 为什么这样写呢?

我们思考当代码中当wait被唤醒的时候, 此时if的条件一定就不成立了吗?

具体来说, 思考put方法中的wait被唤醒, 往下执行是要要求,队列不满但是wait被唤醒了之后, 队列一定是不满的嘛? 其实当前代码中是不会出现这样的问题的, 但是稳妥起见, 最好的办法就是wait唤醒之后再判定一下条件是否满足, 而且Java标准库当中就是建议这么写的.

img

调整部分的代码如下:

//出队部分while (size == items.length) {    //队列满了,阻塞等待    this.wait();}//入队部分while (size == 0) {    //队列空了,阻塞等待    this.wait();}

最后就是测试一下我们所写的这个阻塞队列了, 我们创建两个线程分别是消费者线程customer和生产者线程producer, 生产者生产数字, 消费者消费数字, 为了让执行结果中的阻塞效果明显一些, 我们可以使用sleep方法来控制一下生产者/消费者的生产/消费的频率, 这里我们让开始时生产的速度快一些, 消费的速度慢一些, 全部代码如下:

class MyBlockingQueue {    //存放元素的数数组    private int[] items = new int[1000];    //队头指针    private int head = 0;    //队尾指针    private int tail = 0;    //记录队列元素的个数    private int size = 0;    //入队操作    public void put (int val) throws InterruptedException {        synchronized (this) {            while (size == items.length) {                //队列满了,阻塞等待                this.wait();            }            items[tail++] = val;            //等价于 tail %= items.length            if (tail >= items.length) {                tail = 0;            }            size++;            //唤醒因队列空造成的阻塞wait            this.notify();        }    }    //出队操作    public Integer take() throws InterruptedException {        int resulet = 0;        synchronized (this) {            while (size == 0) {                //队列空了,阻塞等待                this.wait();            }            resulet = items[head++];            //等价于 head %= elem.length            while (head >= items.length) {                head = 0;            }            size--;            //唤醒因队列满造成的阻塞wait            this.notify();            return resulet;        }    }}public class Test {    public static void main(String[] args) {        //消费线程        MyBlockingQueue queue = new MyBlockingQueue();        Thread customer = new Thread(() -> {            while(true) {                try {                    int result = queue.take();                    System.out.println("消费元素: " + result);                    Thread.sleep(500);                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }            }        });        customer.start();        //生产线程        Thread producer = new Thread(() -> {            int count = 0;            while (true) {                try {                    queue.put(count);                    System.out.println("生产元素: " + count);                    count++;                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }            }        });        producer.start();    }}

执行结果:
img

可以看到执行结果中因为生产者生产快, 消费者消费慢, 所以一开始生产者生产的速度是极快的, 当生产到阻塞队列满了之后生产者需要等待消费者消费后才能生产, 此时生产者的速度就跟消费者同步了.

来源地址:https://blog.csdn.net/Trong_/article/details/128570568

--结束END--

本文标题: Java多线程案例之阻塞队列

本文链接: https://www.lsjlt.com/news/389723.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java多线程案例之阻塞队列
    文章目录 一. 认识阻塞队列1. 什么是阻塞队列2. 生产者消费者模型3. 标准库中阻塞队列类 二. 基于循环队列实现的简单阻塞队列1. 循环队列的简单实现2. 阻塞队列的简单实现 ...
    99+
    2023-09-01
    java 面试 阻塞队列 生产者消费者模型 多线程
  • Java多线程案例之阻塞队列详解
    目录一.阻塞队列介绍1.1阻塞队列特性1.2阻塞队列的优点二.生产者消费者模型2.1阻塞队列对生产者的优化三.标准库中的阻塞队列3.1Java提供阻塞队列实现的标准类3.2Block...
    99+
    2022-11-13
    Java多线程阻塞队列 Java 阻塞队列 Java多线程
  • 【JavaEE】多线程案例-阻塞队列
    1. 前言 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是: 在队列为空时,获取元素的线程会等待队列变为非空当队列满时,存储元素的线程会等待队列可用 阻塞队列...
    99+
    2023-09-16
    java-ee java 开发语言 阻塞队列
  • Java多线程之多种锁和阻塞队列
    目录一、悲观锁和乐观锁1.1. 乐观锁1.2. 悲观锁二、公平锁和非公平锁三、可重入锁(递归锁)四、自旋锁五、独占锁(写)/共享锁(读)六、什么是阻塞队列?七、阻塞队列(Blocki...
    99+
    2024-04-02
  • Java多线程之多种锁和阻塞队列的示例分析
    这篇文章给大家分享的是有关Java多线程之多种锁和阻塞队列的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、悲观锁和乐观锁1.1. 乐观锁顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以...
    99+
    2023-06-15
  • Java多线程常见案例分析线程池与单例模式及阻塞队列
    目录一、单例模式1、饿汉模式2、懒汉模式(单线程)3、懒汉模式(多线程)二、阻塞队列阻塞队列的实现生产者消费者模型三、线程池1、创建线程池的的方法(1)ThreadPoolExecu...
    99+
    2024-04-02
  • Java多线程(单例模式,阻塞队列,定时器,线程池)详解
    目录1. 单例模式(singleton pattern)1.1 懒汉模式1.2 饿汉模式2 阻塞队列(blocking queue)2.1 阻塞队列2.2 生产者消费者模型2.3 标...
    99+
    2024-04-02
  • Java 多线程系列Ⅳ(单例模式+阻塞式队列+定时器+线程池)
    多线程案例 一、设计模式(单例模式+工厂模式)1、单例模式2、工厂模式 二、阻塞式队列1、生产者消费者模型2、阻塞对列在生产者消费者之间的作用3、用标准库阻塞队列实现生产者消费者模型4、模...
    99+
    2023-09-17
    java 单例模式 阻塞队列 定时器 线程池 并发编程
  • Java 阻塞队列和线程池原理分析
    目录【1】阻塞队列一、什么是阻塞队列?二、阻塞队列有什么用?三、阻塞队列的简单实用【2】Java 线程池一、我们为什么需要Java 线程池?使用它的好处是什么?二、Java中主要提供...
    99+
    2024-04-02
  • Java并发编程之阻塞队列(BlockingQueue)详解
    目录队列阻塞队列ArrayBlockingQueue重要属性构造方法添加元素add(e)offer(e)put(e)offer(e,time,unit)移除元素take()deque...
    99+
    2024-04-02
  • 详解Java七大阻塞队列之SynchronousQueue
    目录分析其实SynchronousQueue 是一个特别有意思的阻塞队列,就我个人理解来说,它很重要的特点就是没有容量。 直接看一个例子: package dongguabai....
    99+
    2024-04-02
  • Java阻塞队列BlockingQueue详解
    目录队列的类型数据结构阻塞队列 BlockingQueue常见的阻塞队列BlockingQueue APIArrayBlockingQueue 源码简解生产者消费者模式延迟队列 De...
    99+
    2024-04-02
  • Java面试必备之AQS阻塞队列和条件队列
    一.AQS入队规则 我们仔细分析一下AQS是如何维护阻塞队列的,在独占方式获取资源的时候,是怎么将竞争锁失败的线程丢到阻塞队列中的呢? 我们看看acquire方法,这里首先会调用子类...
    99+
    2024-04-02
  • Java并发编程之阻塞队列深入详解
    目录1. 什么是阻塞队列2. 阻塞队列的代码使用3. 生产者消费者模型(1)应用一:解耦合(2)应用二:削峰填谷(3)相关代码4.阻塞队列和生产者消费者模型功能的实现1. 什么是阻塞...
    99+
    2024-04-02
  • Java 阻塞队列BlockingQueue详解
    目录一. 前言二. 认识BlockingQueue三.BlockingQueue的核心方法:四.常见BlockingQueue五. 小结一. 前言 在新增的Concurrent包中,...
    99+
    2024-04-02
  • java 中 阻塞队列BlockingQueue详解及实例
    java 中 阻塞队列BlockingQueue详解及实例BlockingQueue很好的解决了多线程中数据的传输,首先BlockingQueue是一个接口,它大致有四个实现类,这是一个很特殊的队列,如果BlockQueue是空的,从Blo...
    99+
    2023-05-31
    java 阻塞队列 blockingqueue
  • 详解Java中的阻塞队列
    什么是阻塞队列 在数据结构中,队列遵循FIFO(先进先出)原则。在java中,Queue接口定义了定义了基本行为,由子类完成实现,常见的队列有ArrayDeque、LinkedLis...
    99+
    2024-04-02
  • Springboot详解线程池与多线程及阻塞队列的应用详解
    目录一、案例场景二、使用类三、本例说明1.接收web请求2.后台任务处理3.关系说明四、代码1.OrderController2.FlowStarter流程启动器3.FlowMana...
    99+
    2024-04-02
  • Java常见的阻塞队列总结
    Java阻塞队列 阻塞队列和普通队列主要区别在阻塞二字: 阻塞添加:队列已满时,添加元素线程会阻塞,直到队列不满时才唤醒线程执行添加操作 阻塞删除:队列元素为空时,...
    99+
    2024-04-02
  • Java阻塞队列BlockingQueue怎么实现
    这篇文章主要讲解了“Java阻塞队列BlockingQueue怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java阻塞队列BlockingQueue怎么实现”吧!BlockingQ...
    99+
    2023-06-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作