JUC--阻塞队列

news2025/1/14 15:27:01

目录

问题引出

 一.单端阻塞队列(BlockingQueue)

二.双端阻塞队列(BlockingDeque)

三.延迟队列(DelayQueue)


问题引出

由于实现消费者-生产者模型,每一次实现都比较麻烦,比如sychronized的同步处理,或者通过锁实现。这些实现起来都比较繁琐,为了简单就能实现这种模型,JUC提供了阻塞队列接口:BlockingQueue(单端阻塞队列)和BlockingDeque(双端阻塞队列)

 一.单端阻塞队列(BlockingQueue)

原理:通过使用FIFO模式处理的集合结构

什么是FIFO?

FIFO(First-In, First-Out)是一种常见的处理数据的方式,也被称为先进先出模式。在FIFO模式中,首先进入队列的数据首先被处理,而最后进入队列的数据最后被处理。

可以将FIFO模式理解为排队等候的情景,比如在超市的收银台,顾客按照先后顺序排队结账。当一个顾客结完账离开后,下一个顾客才能开始结账。这就是FIFO模式的处理顺序。

在计算机科学中,FIFO模式通常用于数据缓冲区、队列和调度算法等场景。例如,在操作系统中,进程调度算法可以使用FIFO模式,根据进程到达的先后顺序来决定执行顺序;在网络通信中,消息队列可以使用FIFO模式确保消息按照发送的先后顺序被接收和处理。

单端阻塞队列BlockQueue的常用方法:

方法描述
put(item)将指定的项放入队列中,如果队列已满则阻塞,直到有空间可用
take()从队列中获取并移除一个项,如果队列为空则阻塞,直到有项可取
offer(item)尝试将指定的项放入队列中,如果队列已满则立即返回false,否则返回true
poll(timeout)从队列中获取并移除一个项,在指定的超时时间内如果队列为空则返回null
peek()返回队列中的第一个项,但不对队列进行修改,如果队列为空则返回null
size()返回队列中当前的项数
isEmpty()检查队列是否为空
isFull()检查队列是否已满
clear()清空队列,移除所有的项

单端阻塞队列接口BlockingQueue提供多个子类ArrayBlockingQueue(数组结构)、LinkedBlockingQueue(链表单端阻塞队列)、PriorityBlockingQueue(优先级阻塞队列)、SynchronousQueue(同步队列)

ArrayBlockingQueue

案例代码:

上述代码修改后如下

package Example2129;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args){
//        创建对象和资源量
        BlockingQueue<String> queue  = new ArrayBlockingQueue<String>(2);
// 创建一个包含各种美食的String数组
        String[] foods = {"披萨","汉堡", "寿司", "墨西哥炸玉米卷", "牛排", "意大利面", "烤鸭", "富士山寿司", "印度咖喱", "巴西烤肉",};

        int id;
//        两个厨师
        for (int i=0;i<2;i++){
            id = i;
            new Thread(()->{
                for (int j=0;j<10;j++){
                    try {
//                        模拟做菜时间
                        TimeUnit.SECONDS.sleep(3);
                        System.out.println(Thread.currentThread().getName()+"已经做完菜肴"+foods[j]+"并端上座子");
                        queue.put(foods[j]);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            },"厨师"+id).start();
        }
        for (int i=0;i<10;i++){
            id = i;
            new Thread(()->{
                for (int j=0;j<2;j++){
                    try {
//                        模拟客人吃饭的时间
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println(Thread.currentThread().getName()+"享用完"+queue.take()+"这道菜");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            },"客人"+i).start();
        }
    }
}

注意:阻塞队列虽然解决了数据存满则线程等待的情况,但是并没有解决线程并发的问题

LinkedBlockingQueue

案例代码:

package Example2130;

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args) {
//        设置容量
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(2);
        Random random = new Random();
        new Thread(()->{
            while (true){
                try {
                    if (queue.size()==2){
                        System.out.println("队列已满");
                        TimeUnit.SECONDS.sleep(1);
                    }else {
                        TimeUnit.SECONDS.sleep(random.nextInt(3));
                        System.out.println("存入数据");
                        queue.put("存入数据");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(random.nextInt(3));
                    if (queue.isEmpty()){
                        System.out.println("队列空了啊");
                        TimeUnit.SECONDS.sleep(1);
                    }else {
                        System.out.println("取出数据");
                        queue.take();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }).start();
    }
}

PriorityBlockingQueue

package Example2131;

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

public class javaDemo {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
        Random random = new Random();
        new Thread(()->{
            try {
                for (int i=0;i<5;i++){
                    queue.put(random.nextInt(10));
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        new Thread(()->{
            try {
                for (int i=0;i<5;i++){
                    System.out.println("取出数据"+queue.take());
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }
}

PriorityBlockingQueue的特点是:

  • 元素按照优先级进行排序。在示例中,较小的数字具有较高的优先级。
  • 插入和移除操作的时间复杂度为O(logN),其中N为队列中的元素个数。

SynchronousQueue

package Example2132;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args){
//        创建对象和资源量
        BlockingQueue<String> queue  = new SynchronousQueue<>();
// 创建一个包含各种美食的String数组
        String[] foods = {"披萨","汉堡", "寿司", "墨西哥炸玉米卷", "牛排", "意大利面", "烤鸭", "富士山寿司", "印度咖喱", "巴西烤肉",};

        int id;
//        两个厨师
            new Thread(()->{
                for (int j=0;j<10;j++){
                    try {
//                        模拟做菜时间
                        TimeUnit.SECONDS.sleep(3);
                        System.out.println(Thread.currentThread().getName()+"已经做完菜肴"+foods[j]+"并端上座子");
                        queue.put(foods[j]);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            },"厨师").start();

        for (int i=0;i<10;i++){
            id = i;
            new Thread(()->{
                    try {
//                        模拟客人吃饭的时间
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println(Thread.currentThread().getName()+"享用完"+queue.take()+"这道菜");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
            },"客人"+i).start();
        }
    }
}

SynchronousQueue的特点是:

  • 队列没有容量,每次插入操作必须等待对应的删除操作,反之亦然。
  • 插入和删除操作是成对的,即一个元素的插入必须等待其被消费取出。

 实现子类之间的区别:

  1. ArrayBlockingQueue(数组结构阻塞队列):

    • 基于数组实现的有界队列,具有固定容量。
    • 具有公平(FIFO)和非公平(默认)两种策略的可选择性。
    • 内部使用单个锁来实现线程安全。
    • 插入和移除元素的时间复杂度为O(1)。
  2. LinkedBlockingQueue(链表单端阻塞队列):

    • 基于链表实现的可选有界或无界队列。
    • 默认情况下是无界的,但可以指定最大容量来创建有界队列。
    • 内部使用两个锁来实现线程安全,一个用于插入操作,一个用于移除操作。
    • 插入和移除元素的时间复杂度为O(1)。
  3. PriorityBlockingQueue (优先级阻塞队列):

    • 基于堆实现的无界优先级队列。
    • 元素按照优先级进行排序,优先级通过元素的自然顺序或者自定义比较器进行确定。
    • 内部不允许存储null元素。
    • 插入和移除元素的时间复杂度为O(logN),其中N为队列中的元素个数。
  4. SynchronousQueue(同步队列):

    • 一个没有缓冲区的阻塞队列,用于线程之间直接传输元素。
    • 每个插入操作必须等待相应的移除操作,反之亦然。
    • 队列本身不存储元素,仅用于线程之间的数据传递。
    • 插入和移除操作通常具有较高的可伸缩性性能。

二.双端阻塞队列(BlockingDeque)

BlockingDeque ,可以实现FIFO与FILO操作

什么是FILO?

  1. FILO(First-In, Last-Out)是一种数据处理方式,也被称为后进先出模式。在FILO模式中,最后进入的数据会首先被处理,而最先进入的数据会最后被处理。
  2. 可以将FILO模式理解为堆叠物品的情景,比如在一个书架上放置书籍。当我们将一本新书放在书架上时,它会被放在已有书籍的顶部,因此最后放置的书会处于最上方。当我们需要取出一本书时,会优先从顶部取出最后放置的那本书。这符合FILO模式的处理顺序。
  3. 在计算机科学中,FILO模式常用于栈(Stack)数据结构的操作。栈是一种具有特定数据插入和删除规则的数据结构,最后插入的数据会成为栈顶,最先插入的数据会成为栈底。当需要访问或移除数据时,我们通常会先操作栈顶的数据。
  4. 总之,FILO模式即后进先出模式,用于保持数据处理顺序的一种方式。类似于堆叠物品或栈数据结构,最后进入的数据会首先被处理,而最先进入的数据会最后被处理。

BlockingDeque 的常用方法:

方法描述
addFirst(item)将指定的项添加到双端队列的开头,如果队列已满则抛出异常
addLast(item)将指定的项添加到双端队列的末尾,如果队列已满则抛出异常
offerFirst(item)尝试将指定的项添加到双端队列的开头,如果队列已满则立即返回false,否则返回true
offerLast(item)尝试将指定的项添加到双端队列的末尾,如果队列已满则立即返回false,否则返回true
putFirst(item)将指定的项放入双端队列的开头,如果队列已满则阻塞,直到有空间可用
putLast(item)将指定的项放入双端队列的末尾,如果队列已满则阻塞,直到有空间可用
pollFirst(timeout)从双端队列的开头获取并移除一个项,在指定的超时时间内如果队列为空则返回null
pollLast(timeout)从双端队列的末尾获取并移除一个项,在指定的超时时间内如果队列为空则返回null
takeFirst()从双端队列的开头获取并移除一个项,如果队列为空则阻塞,直到有项可取
takeLast()从双端队列的末尾获取并移除一个项,如果队列为空则阻塞,直到有项可取
getFirst()返回双端队列的开头项,但不对队列进行修改,如果队列为空则抛出异常
getLast()返回双端队列的末尾项,但不对队列进行修改,如果队列为空则抛出异常
peekFirst()返回双端队列的开头项,但不对队列进行修改,如果队列为空则返回null
peekLast()返回双端队列的末尾项,但不对队列进行修改,如果队列为空则返回null
size()返回双端队列中当前的项数
isEmpty()检查双端队列是否为空
clear()清空双端队列,移除所有的项

双端阻塞队列只有一个实现的子类LinkedBlockingDeque

案例代码:

package Example2133;

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args) {
        BlockingDeque<Integer> deque = new LinkedBlockingDeque<>();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    deque.putFirst(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(deque.takeLast());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (deque.isEmpty()){
                    System.out.println("队列空了啦");
                    break;
                }
            }
        }).start();
    }
}

可以看到双端情况下可以将数据放在头或者尾,获取也可以获取头和尾


三.延迟队列(DelayQueue)

在JUC中提供自动弹出数据延迟的队列DelayQueue,该类属于BlockingQueue的实现子类。如果是创建类对象插入到延迟队列中的话,类需要继承Delayed,并且覆写 compareTo()和getDelay()方法

原理:

  1. 延迟时间计算:每个元素实现了 Delayed 接口,该接口定义了一个 getDelay(TimeUnit unit) 方法,用于计算当前元素距离延迟时间还有多长时间。这个方法返回一个 long 类型的时间值,表示时间单位内的延迟时间。

  2. 队列存储:内部使用有序优先队列(PriorityQueue)来存储元素。元素将根据它们的延迟时间进行排序,即最小的延迟时间的元素将排在队头。

  3. 元素添加:调用 offer(E e) 方法将一个元素添加到队列中。插入元素时,根据其延迟时间,决定其位置。

  4. 元素获取:调用 take() 方法从队列中取出延迟时间到达的元素。如果队列为空,则线程阻塞等待,直到有元素可以取出。

  5. 添加与移除的同步:对队列的添加和移除操作进行同步,以确保多线程环境下的安全性。

  6. 定时删除:元素在队列中的保存时间一旦超过其延迟时间,将会被自动删除。

常用方法:

方法名描述
enqueue(item, delay)将指定的 item 入队,并在 delay 毫秒后执行。
dequeue()出队并返回最早的延迟任务。
getDelay(item)返回指定 item 的剩余延迟时间(以毫秒为单位),如果 item 已经过期则返回负数。
remove(item)从队列中移除指定的 item
size()返回队列中延迟任务的数量。
isEmpty()判断队列是否为空。
clear()清空队列,移除所有的延迟任务。
getExpiredItems(now)返回所有已过期的任务,并从队列中移除它们。
getNextExpiringItem()返回下一个即将过期的任务,但不从队列中移除它。

 案例代码:

package Example2134;

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

class Student implements Delayed {
    private String name;
//    设置停留时间
    private  long delay;
//    设置离开时间
    private long expire;
    Student(String name, long delay , TimeUnit unit){
    this.name=name;
    this.delay = TimeUnit.MILLISECONDS.convert(delay,unit);
    this.expire = System.currentTimeMillis()+this.delay;
    }

    @Override
    public String toString() {
        return this.name+"同学已经到达预计停留的时间"+TimeUnit.SECONDS.convert(this.delay,TimeUnit.MILLISECONDS)+"秒,已经离开了";
    }

//    延迟时间计算
    @Override
    public long getDelay(@NotNull TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }

//    队列弹出计算
    @Override
    public int compareTo(@NotNull Delayed o) {
        return (int) (this.delay-this.getDelay(TimeUnit.MILLISECONDS));
    }
}

public class javaDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Student> students = new DelayQueue<Student>();
        students.put(new Student("黄小龙",3,TimeUnit.SECONDS));
        students.put(new Student("张三",1,TimeUnit.SECONDS));
        students.put(new Student("李四",5,TimeUnit.SECONDS));
        while (!students.isEmpty()){
            Student stu = students.take();
            System.out.println(stu);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/915004.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023河南萌新联赛第(六)场:河南理工大学 B - 这是dp题吗

2023河南萌新联赛第&#xff08;六&#xff09;场&#xff1a;河南理工大学 B - 这是dp题吗 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld 题目描述 小l同学非常喜欢三角形&#x…

淘宝商品优惠券详情item_get_app-获得淘宝app商品详情原数据

item_get_app-获得淘宝app商品详情原数据 taobao.item_get_app 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;调用API接口入口secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09…

等级发布/查询平台

在传统的教学模式下&#xff0c;老师们需要手动把成绩挨个私发给学生家长&#xff0c;这不仅费时费力&#xff0c;还容易出现错误。然而&#xff0c;随着科技的不断发展&#xff0c;易查分系统的出现为教师提供了一个高效准确的成绩发布工具。 系统优势 1. 高效便捷&#xff1a…

【数据结构与算法】弗洛伊德算法

弗洛伊德算法 介绍 和 Dijkstra 算法一样&#xff0c;弗洛伊德&#xff08;Floyd&#xff09;算法也是一种用于寻找给定的加权图中顶点间最短路径的算法。弗洛伊德算法计算图中各个顶点之间的最短路径。迪杰斯特拉算法用于计算图中某一个顶点到其他顶点的最短路径。弗洛伊德算…

贪吃蛇大作战技术报告(JAVA)

完整作品见链接&#xff1a;JAVA贪吃蛇大作战大作业_Java贪吃蛇如何实现向左不能向右资源-CSDN文库 猫猫大作战技术报告 1、项目概述 本项目的编译环境为jdk-19&#xff0c;用到的编译器为Eclipse&#xff0c;采用图形化编程&#xff0c;基于JAVA的Swing库进行界面的绘制&…

解决Pandas KeyError: “None of [Index([...])] are in the [columns]“问题

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

使用ctcloss训练矩阵生成目标字符串

首先我们需要明确 c t c l o s s ctcloss ctcloss是用来做什么的。比如说我们要生成的目标字符串长度为 l l l&#xff0c;而这个字符串包含 k k k个字符&#xff0c;字符串允许的最大长度为 L L L&#xff0c;这里我们认为一个位置是一个时间步&#xff0c;就是一拍&#xff0…

PyTorch三种主流模型构建方式:nn.Sequential、nn.Module子类、nn.Module容器开发实践,以真实烟雾识别场景数据为例

Keras和PyTorch是两个常用的深度学习框架&#xff0c;它们都提供了用于构建和训练神经网络的高级API。 Keras: Keras是一个高级神经网络API&#xff0c;可以在多个底层深度学习框架上运行&#xff0c;如TensorFlow和CNTK。以下是Keras的特点和优点&#xff1a; 优点&#xff…

解决git上传远程仓库时的最大文件大小限制

git默认限制最大的单文件100M&#xff0c;当某个文件到达50M时会给你提示。解决办法如下 首先&#xff0c;打开终端&#xff0c;进入项目所在的文件夹&#xff1b; 输入命令&#xff1a;git config http.postBuffer 524288000 执行完上面的语句后输入&#xff1a;git config…

Stable Diffusion 系列教程 | 图生图基础

前段时间有一个风靡全网的真人转漫画风格&#xff0c;受到了大家的喜欢 而在SD里&#xff0c;就可以通过图生图来实现类似的效果 当然图生图还有更好玩的应用&#xff0c;我们一点一点来探索 首先我们来简单进行一下图生图的这一个实践---真人转动漫 1. 图生图基本界面 和…

代码之美:探索可维护性的核心与实践

为什么可维护性如此重要 项目的长期健康 在软件开发的早期阶段&#xff0c;团队可能会对代码的可维护性不太重视&#xff0c;因为他们更关心的是功能的快速交付。但随着时间的推移&#xff0c;随着代码库的增长和复杂性的增加&#xff0c;不重视代码的可维护性可能会导致严重的…

docker使用安装教程

docker使用安装教程 一、docker安装及下载二、使用教程2.1 镜像2.2 容器2.3 docker安装Redis 一、docker安装及下载 一、安装 安装执行命令&#xff1a;curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun 二、启停常用命令 启动docker,执行命令&#xf…

分支和循环语句-C语言(初阶)

目录 一、什么是语句 二、分支语句 2.1 if语句 2.2 switch语句 三、循环语句 3.1 while循环 3.2 for循环 3.3 do...while循环 一、什么是语句 C语言语句有五类&#xff1a;表达式语句、函数调用语句、控制语句、复合语句、空语句。 控制语句用于控制程序的执行流程&#xff0…

在vue3+ts+vite中使用svg图片

目录 前言 步骤 1.安装svg-sprite-loader,这里使用的是6.0.11版本 2.项目的svg图片存放在src/icons下&#xff0c;我们在这里创建两个文件index.ts和index.vue&#xff08;在哪创建和文件名字并没有任何要求&#xff09; 3.在index.ts中加入下列代码(如果报错找不到fs模块请…

Redis的基本操作

文章目录 1.Redis简介2.Redis的常用数据类型3.Redis的常用命令1.字符串操作命令2.哈希操作命令3.列表操作命令4.集合操作命令5.有序集合操作命令6.通用操作命令 4.Springboot配置Redis1.导入SpringDataRedis的Maven坐标2.配置Redis的数据源3.编写配置类&#xff0c;创还能Redis…

ubuntu修改默认文件权限umask

最近在使用ubuntu的过程中发现一个问题&#xff1a; 环境是AWS EC2&#xff0c;登录用户ubuntu&#xff0c;系统默认的umask是027&#xff0c;修改/etc/profile文件中umask 027为022后&#xff0c;发现从ubuntu用户sudo su过去root用户登录查询到的umask还是027&#xff0c;而…

2023-8-22 单调栈

题目链接&#xff1a;单调栈 #include <iostream>using namespace std;const int N 100010;int n; int stk[N], tt;int main() {cin >> n;for(int i 0; i < n; i ){int x;cin >> x;while(tt && stk[tt] > x) tt--;if(tt) cout << st…

第十章,搜索模块

10.1添加搜索框 <template><div class="navbar-form navbar-left hidden-sm"><div class="form-group"><inputv-model.trim="value"type="text"class="form-control search-input mac-style"placeho…

数据传输过程

2 数据传输过程 了解网络中常用的分层模型后&#xff0c;现在来学习一下数据在各层之间是如何传输的。 2.1数据封装与解封装过程(一) 下面我们将以TCP/IP五层结构为基础来学习数据在网络中传输的“真相”。由于这个过程比较 抽象&#xff0c;我们可以类比给远在美国的朋友邮寄…

人工智能深度估计技术

人工智障&#xff08;能&#xff09;走起&#xff01;&#xff01;&#xff01; 下面是基本操作&#xff1a; 在Hugging Face网页中找到Depth Estimation的model&#xff0c;如下图&#xff1a; Hugging Face – The AI community building the future. &#xff08;上Huggin…