阻塞队列和定时器的使用

阻塞队列

谈到队列,大家就能想到队列的先进先出原则,但有些特殊的队列,虽然也是先进先出的,但是带有阻塞功能,我们把这种队列叫做阻塞队列.

★如果队列为空,执行出队操作就会阻塞,阻塞到另外一个线程往队列里添加元素(队列不为空)为止.

★如果队列满了,执行入队操作时,也会阻塞,阻塞到另外一个线程从队列里取出元素(队列不满).

想要了解阻塞队列,先了解生产者-消费者模型,这个模型给我们程序带来了非常重要的好处

1.生产者-消费者模型

在一个餐馆中,有多名厨师(生产者)负责烹饪菜品,并将其放在共享的出菜区(缓冲区)上。而服务员(消费者)则从出菜区上取出菜品并端给顾客。这里的厨师可以看作是生产者,出菜区是缓冲区,服务员是消费者。

1. 实现了生产者和消费者之间的 "解耦"

我们写代码要追求低耦合,所谓解耦操作,就是降低耦合的过程.

        比如过年一家人一起包饺子. 一般都是有明确分工 , 比如一个人负责擀饺子皮 , 其他人负责包 . 擀饺 子皮的人就是 "生产者", 包饺子的人就是 " 消费者 ".
        擀饺子皮的人不关心包饺子的人是谁( 能包就行 , 无论是手工包 , 借助工具 , 还是机器包 ), 包饺子的人 也不关心擀饺子皮的人是谁( 有饺子皮就行 , 无论是用擀面杖擀的 , 还是拿罐头瓶擀 , 还是直接从超市买的)
上述操作生产者和消费者没有直接联系,而是通过阻塞队列相互关联,生产者和消费者有一方出现了问题,不会影响对方的操作,这样操作就降低了耦合.

2.可以"削峰填谷",保证系统的稳定性

阻塞队列就相当于一个缓冲区,平衡了生产者消费者的处理能力

比如在 " 秒杀 " 场景下 , 服务器同一时刻可能会收到大量的支付请求 . 如果直接处理这些支付请求 , 服务器可能扛不住( 每个支付请求的处理都需要比较复杂的流程 ). 这个时候就可以把这些请求都放 到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求 .
这样做可以有效进行 " 削峰 ", 防止服务器被突然到来的一波请求直接冲垮

2.标准库中的阻塞队列

java标准库中内置了阻塞队列,在使用的时候直接使用标准库中的就行

1.BlockingDeque是一个接口

2.真正实现BlockingDeque的类有三种

3.带有阻塞功能的方法.put()方法用于阻塞队列的入队,take()方法用于阻塞队列的出队.在使用take的时候需要处理异常

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) {
                    e.printStackTrace();
                }
            }
        });
        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) {
                    e.printStackTrace();
                }
            }
        });
        producer.start();
    }

上面执行的结果是

 上面代码先执行消费元素是因为当customer线程执行到blockingQueue,take()时发生了阻塞,等到producer线程执行blockingQueue.put()后,两个线程并发执行,谁先打印都有可能.

4. 不带阻塞功能的方法 入队列 offer(), 出队列 poll(),  取队首元素 peek().

3.模拟实现阻塞队列

实现阻塞队列的前提是我们要先实现一个普通的队列,实现普通队列的方法有两种,一个是基于链表的队列,采用头删法和尾插法实现一个阻塞队列,另外一个方法是基于数组的循环队列,两个方法的具体实现在下面这篇文章里面.

循环队列的实现.​​​​​​​

要模拟实现阻塞队列,前提是在多线程的基础上,通过synchronized关键字对put()和take()方法中的代码进行加锁,然后通过wait()和notify()方法对插入元素和取出元素进行阻塞等待.具体方法是,当插入元素时如果队列满了,就产生阻塞等待,直到有元素取出后唤醒put中的wait().当取出元素的时候,如果队列为空,让take()方法产生阻塞等待,只到有元素插入后,唤醒take()中的wait().

class MyBlockingQueue {
    private int[] item = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //插入元素
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            while(size == item.length) {
                //队列满了,产生阻塞等待
                this.wait();
            }
            item[tail] = value;
            tail++;
            if(tail >= item.length) {
                tail = 0;
            }
            size++;
            //唤醒take中的wait()
            this.notify();
        }
    }

    //取出元素
    public Integer take() throws InterruptedException {
        int result = 0;
        synchronized (this) {
            while(size == 0) {
                //队列为空,产生阻塞等待
                this.wait();
            }
            result = item[head];
            head++;
            if(head >= item.length) {
                head = 0;
            }
            size--;
            //唤醒put中的wait()
            this.notify();
        }
        return result;
    }
}
上面代码中put()和take()不会同时产生阻塞等待,因为我们是在同一个对象中,一个队列不可能同时满足队列为空和队列为满.

定时器

定时器就好比是我们生活中的闹钟,当我们被早八的闹钟叫醒后想在床上在躺十分钟,我们设置闹钟就可以让十分钟再响,而我们的计时器具有类似的功能,是指定一段之间后执行一段代码.

在Java的标准库中有一个Timer类,Timer类的核心方法是schedule()

schedule()中有连个参数,第一个参数指定定时器要执行的任务,第二个参数指定多长时间后执行这个任务

观察代码结果发现,在 "程序启动" 打印后3000ms才打印  "运行定时器任务".

模拟实现定时器

主要实现下面几种功能

1.让被注册的任务,能在指定时间后执行.为了实现这个功能可以单独在定时器内部建一个线程,让这个线程周期性的扫描,判断任务时间是否到了,如果到了,就执行,若没动,就继续等待.这n个任务,可以用优先级队列来保存,按照指定时间小的优先级高,就放在队头,此时扫描线程,就只需要扫描队头元素,对头元素时间没到的话,其他时间更不可能到.

在优先级队列中,我们是基于时间进行比较的,可以实现 Comparable接口来进行比较

在扫描线程中会出现下面的bug.

2.一个定时器可以指定N个任务,N个任务会按照指定时间按顺序执行.

具体实现代码

class MyTask implements Comparable<MyTask>{
    //要执行的任务
    private Runnable runnable;
    //任务在什么时间执行(用毫秒时间戳表示)
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime() {
        return time;
    }
    //执行任务
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time - o.time);
    }
}


//定时器
class MyTimer {
    //扫描线程
    private Thread t = null;
    //有一个阻塞优先级队列
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public MyTimer() {
        t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (this) {
                        MyTask myTask = queue.take();
                        //取出队头元素观察判断是否到时间了
                        long curTime = System.currentTimeMillis();
                        if(curTime < myTask.getTime()) {
                            //时间没到放回队列
                            queue.put(myTask);
                        } else {
                            //时间到了,执行任务
                            myTask.run();
                            this.wait(myTask.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        } );
        t.start();
    }


    public void schedule (Runnable runnable,long after) {
        //第一个参数 是指定的任务
        // 第二个参数是在多少毫秒后执行
        MyTask myTask = new MyTask(runnable,System.currentTimeMillis()+ after);
        //把任务放到队列里
        queue.put(myTask);
        synchronized (this) {
            this.notify();
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

“GUI图形化界面的魅力、SSH协议的安全通信与IDEA集成Git的高效开发“

文章目录 引言一、GUI图形化界面的实际应用二、SSH协议的安全通信什么是SSH?git/github生成密钥并通过远程github仓库配置 三、IDEA集成Git的快速上手指南总结 引言 在计算机科学领域&#xff0c;图形用户界面&#xff08;GUI&#xff09;是一种以图形方式呈现信息和交互的用…

Flutter笔记:绘图示例 - 一个简单的(Canvas )时钟应用

Flutter笔记 绘图示例 - 一个简单的&#xff08;Canvas &#xff09;时钟应用 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_2855…

大数据毕业设计选题推荐-污水处理大数据平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

【数据结构】Lambda

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈数据结构 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; Lambda表达式 1. 背景1.1 语法1.2 函…

【C++优先队列使用】问题总结

说明&#xff1a; 文章内容为关于priority_queue的使用总结&#xff0c;在C中要包含头文件<queue>文章内容为个人的学习整理&#xff0c;如有错误&#xff0c;欢迎指正。 文章目录 1. 优先队列默认是大根堆2. 关于优先队列和sort的比较逻辑2.1 sort的比较逻辑2.2 优先队…

python操作链接数据库和Mysql中的事务在python的处理

python操作数据库 pymysql模块: pip install pymysql作用:可以实现使用python程序链接mysql数据库&#xff0c;且可以直接在python中执行sql语句 添加操作 import pymysql #1.创建链接对象c conn pymysql.Connect(host127.0.0.1,#数据库服务器主机地址port3306, #mysql的端口…

一篇文章让你了解Java中的继承

目录 继承一.什么是继承二.为什么要使用继承三.继承的语法四.继承中有重复怎么办&#xff1f;1.**访问原则** 五.super和this1.**this**2.**super**3.**super注意事项**4.**super和this异同点**六.构造方法的引入1.父类不带参数的构造方法2.父类带有参数的构造方法 七.继承中的…

【二叉树】如何构建一个包含大量随机数节点的二叉树测试用例

【二叉树】如何构建一个包含大量随机数节点的二叉树测试用例 前言一、案例准备二、自动生成随机二叉树工具类&#xff08;TreegenerateUtils&#xff09;三、如何调用随机二叉树工具类&#xff08;TreegenerateUtils&#xff09;&#xff1f; 前言 今天笔者在测试有关二叉树的…

Leetcode-206 反转链表

迭代法&#xff1a;将指针方向依次改变&#xff0c;定义两个指针pre和cur /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, Lis…

Bengio担任一作,联手一众图灵奖得主,预防AI失控,扛起AI监管大旗

图灵奖得主最近都在关心些什么呢&#xff1f;Yoshua Bengio&#xff0c;深度学习的奠基人之一&#xff0c;前几天他担任一作&#xff0c;联合多位大佬&#xff0c;发文探讨了如何在人工智能&#xff08;AI&#xff09;快速发展的时代管控相关风险&#xff0c;共同寻求当下生成式…

Flink SQL -- 命令行的使用

1、启动Flink SQL 首先启动Flink的集群&#xff0c;选择独立集群模式或者是session的模式。此处选择是时session的模式&#xff1a;yarn-session.sh -d 在启动Flink SQL的client&#xff1a; sql-client.sh 2、kafka SQL 连接器 在使用kafka作为数据源的时候需要上传jar包到…

python+requests接口自动化测试

原来的web页面功能测试转变成接口测试&#xff0c;之前大多都是手工进行&#xff0c;利用postman和jmeter进行的接口测试&#xff0c;后来&#xff0c;组内有人讲原先web自动化的测试框架移驾成接口的自动化框架&#xff0c;使用的是java语言&#xff0c;但对于一个学java&…

Linux学习之进程三

目录 进程控制 fork函数 什么是写时拷贝 进程终止 mian函数的返回值 退出码 错误码 exit() 进程等待 1.什么是进程等待&#xff1f; 2.为什么要进行进程等待&#xff1f; 3.如何进程进程等待&#xff1f; wait&#xff0c;waitpid&#xff1a; waitpid 进程替换 …

Lua更多语法与使用

文章目录 目的错误处理元表和元方法垃圾回收协程模块面向对象总结 目的 在前一篇文章&#xff1a; 《Lua入门使用与基础语法》 中介绍了一些基础的内容。这里将继续介绍Lua一些更多的内容。 同样的本文参考自官方手册&#xff1a; https://www.lua.org/manual/ 错误处理 下…

node插件MongoDB(四)—— 库mongoose 操作文档使用(新增、删除、更新、查看文档)(二)

文章目录 前言&#xff08;1&#xff09;问题&#xff1a;安装的mongoose 库版本不应该过高导致的问题&#xff08;2&#xff09;重新安装低版本 一、插入文档1. 代码2. node终端效果3. 使用mongo.exe查询数据库的内容 二、删除文档1. 删除一条2. 批量删除3. 代码 三、修改文档…

Go基础知识全面总结

文章目录 go基本数据类型bool类型数值型字符字符串 数据类型的转换运算符和表达式1. 算数运算符2.关系运算符3. 逻辑运算符4. 位运算符5. 赋值运算符6. 其他运算符运算符优先级转义符 go基本数据类型 bool类型 布尔型的值只可以是常量 true 或者 false。⼀个简单的例⼦&#…

MIPSsim模拟器 使用说明

&#xff08;一&#xff09; 启动模拟器 双击MIPSsim.exe&#xff0c;即可启动该模拟器。模拟器启动时&#xff0c;自动将自己初始化为默认状态。所设置的默认值为&#xff1a; u所有通用寄存器和浮点寄存器为全0&#xff1b; u内存清零&#xff1b; u流水寄存器为全0&#xff…

C++结构体定义 创建 赋值 结构体数组 结构体指针 结构体嵌套结构体

结构体是什么&#xff1f; struct是自定义数据类型&#xff0c;是一些类型集合组成的一个类型。结构体的定义方式 #include<iostream> using namespace std;struct Student {string name;int age;int score; };创建结构体变量并赋值 方式一&#xff0c;先创建结构体变…

基于springboot+vue开发的教师工作量管理系

教师工作量管理系 springboot31 源码合集&#xff1a;www.yuque.com/mick-hanyi/javaweb 源码下载&#xff1a;博主私 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了教师工作量管理系统的开发全过程。通过…

人工智能技术的高速发展,普通人如何借助AI实现弯道超车?

人工智能技术的高速发展&#xff0c;普通人如何借助AI实现弯道超车&#xff1f; 随着互联网信息传播的爆炸&#xff0c;人类科技文明的快速发展“人工智能”成为新的话题&#xff0c;科技的进步也让普通人觉得自己与社会脱节&#xff0c;找工作越来越难&#xff0c;创业越来越难…