阻塞队列的安全实现,定时器的安全实现(面试易考),超详细

news2024/12/29 10:54:54

 一、💛

如何实现一个线程安全的阻塞队列

目前,当前代码是循环队列(还没有进行改动)

head和tail的判空判断满两种方法:

1.浪费一个格子,当前走到head的前一个位置,就认为队列满的

2.单独搞一个变量,去记录当前的元素个数  (下面的代码是使用的这个)

class MyBlockingQueue{
    private  String[] items=new String[1000];
    //队列的头
    private int head=0;
    //描述队列的下一个元素,也就是正常队列的队尾
    private int tail=0;
    //统计多少个数量
    public  int size=0;
    //插入
    public void put(String elem){
        if(size>=items.length){
            return ;
        }

        items[tail]=elem;
        tail++;
        if(tail>=items.length){     //这一步看我下面解析
            tail=0;
        }
        size++;

    }
    //取出队头元素
    public String take(){
        if(size==0){
            return null;
        }
        String elem=items[head];
        head++;
        if(head> items.length){     //这一步看我下面解析
            head=0;
        }
        size--;
        return elem;
    }
}

在上述代码中,

if(tail>=items.length){   
            tail=0;
        }这个代码和我们平时的循环队列是不是发现有点不一样,我们平时的选择是下面这个

tail=(tail+1)%item.length 但是你实际一想,他不就是把越界的给恢复到从0开始吗,所以结果一致,而且 ,我们写代码是两个目的:

1.让写出来的代码,开发效率高(好去理解,好修改)

2.让写出的代码,运行效率高(执行速度快)  另外再补充一个小知识。cpu运算加,减,位运算,是比乘除更快一些的,乘除假如是针对(2的幂)的乘除运算,可以被优化成为位运算。

二、💙 

改造(上面写的战损版)阻塞队列:

1.线程安全问题:加锁解决(直接无脑加锁(打包成原子),修改变量(count++这个类型的)的地方太多了)

2.内存可见性问题,一个线程在改,另一个线程读,内存可见性是否能够观察到修改

3.阻塞等待,wait,notify(让他等待,而不是直接返回值啥的)(插入的时候,假如说满就让他等待一会,直到你出队列,空的时候就等一会,直到你插入队列。)

class MyBlockingQueue{
    private  String[] items=new String[1000];
    //队列的头
    private  volatile int head=0;
    //描述队列的下一个元素,也就是正常队列的队尾
    private volatile int tail=0;
    //统计多少个数量
    public  volatile int size=0;
    //插入
    public void put(String elem) throws InterruptedException {
        synchronized (this) {
            while(size >= items.length) {
                this.wait();               //1号(满了就阻塞等待 4号会来唤醒1号)
            }

            items[tail] = elem;
            tail++;
            if (tail >= items.length) {
                tail = 0;
            }
            size++;
            this.notify();                //2号(2号来唤醒,3号)
        }
    }
    //取出队头元素
    public String take() throws InterruptedException {
        synchronized (this) {
            while(size == 0) {
                this.wait();            //3号(空了,就阻塞等待,等2号)
            }
            String elem = items[head];
            head++;
            if (head > items.length) {
                head = 0;
            }
            size--;
            this.notify();              //4号(4唤醒1号
            return elem;
        }
    }
}

使用wait时候,建议搭配while使用

原因:put操作之后因为队列满,进入wait阻塞了,但是过一会wait被唤醒,唤醒的时候,队列一定就是非满状态吗,万一唤醒还是满着的,此时继续执行,不就会把之前的元素覆盖了吗。

wait是一定被notify唤醒吗?

在当前代码中,如果是interrupet唤醒会报异常,方法就会结束了,但是也就不会导致刚才说的覆盖已有元素问题。但是⚠️ 假如我拿try catch阁下该如何应对捏?——一旦是被interrupt唤醒,catch执行完毕,继续执行,也就触发了“覆盖元素”的逻辑(我们这个抛异常操作是碰巧蒙对了)

所以解决方法,拿while更加万无一失

最终下图的代码:也会理解前面的一些东西

import java.util.Scanner;
import java.util.concurrent.*;

class MyBlockingQueue{
    private  String[] items=new String[1000];
    //队列的头
    private  volatile int head=0;
    //描述队列的下一个元素,也就是正常队列的队尾
    private volatile int tail=0;
    //统计多少个数量
    public  volatile int size=0;
    //插入
    public void put(String elem) throws InterruptedException {
        synchronized (this) {
            if(size >= items.length) {
                this.wait();               //1号
            }

            items[tail] = elem;
            tail++;
            if (tail >= items.length) {
                tail = 0;
            }
            size++;
            this.notify();                //2号
        }
    }
    //取出队头元素
    public String take() throws InterruptedException {
        synchronized (this) {
            if (size == 0) {
                this.wait();            //3号
            }
            String elem = items[head];
            head++;
            if (head > items.length) {
                head = 0;
            }
            size--;
            this.notify();              //4号
            return elem;
        }
    }
}
public  class  Demo {
    public static void main(String[] args)  {
       MyBlockingQueue myBlockingQueue=new MyBlockingQueue();
        Thread t1=new Thread(()->{
            int count =0;
            while (true){
                try {
                    myBlockingQueue.put(String.valueOf(count));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产了元素"+count);
                count++;

            }
        });
        Thread t2=new Thread(()->{
            while(true) {
                Integer n = null;
                try {
                    n = Integer.valueOf(myBlockingQueue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者" + n);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

 和前面的生产者消费者模型,但是我想说的是,控制节奏,t1会快速执行到1000,后按照t2的sleep的节奏一点一点生产。


三、💜

定时器:日常开发中,常用到的组件(前后端都要用的定时器,类似于一个闹钟)

Timer类(util包)

public class Demo1 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {             //给Timer中注册的任务,不是在调用
            @Override                             schedule的线程中执行的,而是通过Timer     
            public void run() {                 内部的线程来执行的   
                System.out.println("hello");
            }
        },3000);                            //过了这些时间才开始执行的
     }
}

注意:schedule可以设置多个任务,任务的完成先后是根据等待时间的

四、💚

如何我们自己实现一个定时器(闹钟):首先先要把任务描述出来,再用数据结构来把多个任务组织起来。

1.创建一个TimerTask这样的类,表示一个任务,该任务需包含两方面,任务的内容,任务的实际执行时间->通过时间戳表示:在schedule的时候,先获取当前系统时间,在这个基础上,加上(延时执行),得到了真实要执行这个任务的时间

2.使用一定的数据结构,把多个TimerTask组织起来

思路1(看看行不行):咱们用(链表/数组)组织TimerTask,如果任务特别多,如何确定哪个任务,何时能够执行呢?这时候就需要搞一个线程,不断的去遍历List,进行遍历,看着里面的每个元素,是否到了时间,时间到就执行,不到就跳过下一个。

回答:不行,这个思路可以,但是非常不好,假如任务执行时间都特别长,你这段时间一直都在遍历,扫描,会很消耗cpu(还没做事)。

思路2(正解):

1.并不需要扫描全部的任务,我们其实只需要关注一个,就是最早,时间最靠前的任务即可,最前面的任务假如还没到达的话,其他的任务更不会到达!!!

遍历一个->只关注一个,那么也就是说最好能选出最大,最小这种的数据结构(脑子里第一个肯定就是——堆(优先级队列))。

2.针对这个任务扫描,也不用一遍一遍一直重复执行,我们获取完队首时间(最小的任务时间)和当前时间做差值,在这个差值到达之前,可以让他休眠或者等待,这样不会进行重复的扫描,大幅度降低了扫描的次数->这样也不消耗cpu,(此处提高效率,不是缩短执行时间(定时器,时间是固定的),减少了资源利用率,避免了不必要的浪费)

战损没有改良的简版。

class MyTimertask{
    private long time;                 //任务什么时候执行,毫秒级的时间戳
    private Runnable runnable;         //具体的任务
    public MyTimertask(Runnable runnable,long delay){
        time=System.currentTimeMillis()+delay;
        this.runnable=runnable;
    }

    public long getTime() {
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }
}
class MyTimer {
    //使用优先级队列
    private PriorityQueue<MyTimertask> queue = new PriorityQueue<>();

    //定时器核心方法
    public void schedule(Runnable runnable, long delay) {
        MyTimertask task = new MyTimertask(runnable, delay); 
        queue.offer(task);
    }
    //定时器还要搞个扫描方法,内部的线程。一方面负责监控队首元素是否到点了,是否应该执行,
    // 一方面当任务到了之后,就要调用这里的Runable的run方法执行任务
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                //队列为空,此时不可以取元素
                if (queue.isEmpty()) {
                    continue;
                }
                MyTimertask Task = queue.peek();
                long curTime = System.currentTimeMillis();
                if (curTime >= Task.getTime()) {
                    queue.poll();
                    Task.getRunnable().run();
                } else {
                    try {
                        Thread.sleep(Task.getTime() - curTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
         t.start();
    }
}

当前战损版的问题:

1.线程不安全,PriorityQueue<MyTimeTask> queue=new PriorityQueue<>();

这个集合类不是线程安全的,他这个既在线程中使用,又在扫描线程中使用,给针对queue的操作进行加锁。(直接无脑暴力加锁)

2.扫描线程中的sleep是否合理呢?

(1)sleep阻塞后,不会释放锁(休眠的时候,最短的时间都还很长,这么大段时间无法添加任务)影响其他线程执行添加

(2)slepp在休眠过程中,不方便提前中断,(虽然可以用interrupt来中断,单用interrupt意味线程应该结束了)

 假设当前最近的任务是14:00执行,当前时刻是13:00(sleep一小时),此时新增一个新的任务,新任务13:30要执行(成为了最早的任务),所以为了中断,应该使用wait

(3)我们设置的时候,需要把可比较写上,也就是说Comparable/Comparaor,也要知道,是什么需要比较呢,时间(顺序记不清无所谓,试一下就OK了)。

import java.util.PriorityQueue;

//创建一个类,用来描述定时器中的一个任务
class MyTimertask implements Comparable<MyTimertask>{
    private long time;                 //任务什么时候执行,毫秒级的时间戳
    private Runnable runnable;         //具体的任务
    public MyTimertask(Runnable runnable,long delay){
        time=System.currentTimeMillis()+delay;
        this.runnable=runnable;
    }

    public long getTime() {
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    @Override
    public int compareTo(MyTimertask o) {
        return  (int)(this.time-o.time);
    }
}
class MyTimer {
    //使用优先级队列
    private PriorityQueue<MyTimertask> queue = new PriorityQueue<>();

    //定时器核心方法
    public void schedule(Runnable runnable, long delay) {
        synchronized (this) {
            MyTimertask task = new MyTimertask(runnable, delay);
            queue.offer(task);                     //新来一个任务,就需要把线程唤醒
            this.notify();
        }
    }
    //定时器还要搞个扫描方法,内部的线程。一方面负责监控队首元素是否到点了,是否应该执行,
    // 一方面当任务到了之后,就要调用这里的Runable的run方法执行任务
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    //队列为空,此时不可以取元素
                    while (queue.isEmpty()) {
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    MyTimertask Task = queue.peek();               //任务的时间
                    long curTime = System.currentTimeMillis();     //当前的系统时间
                    if (curTime >= Task.getTime()) {               //系统时间
                        queue.poll();               14:01>任务时间14:00 ——执行任务
                        Task.getRunnable().run();
                    } else {
                        try {
                          this.wait(Task.getTime() - curTime);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
         t.start();
    }
}
public class Demo3 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }, 3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("god");
            }
        }, 2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("good");
            }
        }, 1000);
    }








}

这应该是自从数据结构以来,我们学过最难的了,所以大家多敲几遍理解理解

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

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

相关文章

【分布式系统】聊聊分布式事务中原子性

什么是分布式事务 在分布式系统中&#xff0c;一个是计算问题&#xff0c;也就是将多个任务&#xff0c;通过流控技术把不同的流量分发给不同的服务器进行处理。另一个就是存储&#xff0c;而只要设计的存储&#xff0c;就必然会引入从单体事务中衍生除的分布式事务问题。 事务…

css flex 上下结构布局

display: flex; flex-flow: column; justify-content: space-between;

战略方法论

父文章 人人都是战略家 2018年注册会计师公司战略与风险考点:swot分析_知识点_注册会计师 SWOT分析 一、基本原理 所谓SWOT分析&#xff0c;即基于内外部竞争环境和竞争条件下的态势分析&#xff0c;就是将与研究对象密切相关的各种主要内部优势、劣势和外部的机会和威胁等…

提高WordPress网站性能的24个技巧

你想加速你的WordPress网站吗&#xff1f;快速加载页面可改善用户体验、增加页面浏览量并帮助你优化WordPress SEO。在本文中&#xff0c;我们将分享最有用的WordPress网站性能速度优化技巧&#xff0c;以提高WordPress网站性能并加快你的网站速度。 与其他“X 优秀的 WordPres…

redis学习笔记(九)

文章目录 python对redis基本操作&#xff08;1&#xff09;连接redis&#xff08;2&#xff09;数据类型操作 python对redis基本操作 &#xff08;1&#xff09;连接redis # 方式1 import redisr redis.Redis(host127.0.0.1, port6379) r.set(foo, Bar) print(r.get(foo))# …

Xilinx DDR3学习总结——1、MIG核设置

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Xilinx DDR3学习总结——1、MIG核设置 前言开发板 DDR信息MIG 设置前言 话说之前从来没有使用过DDR,工作中的项目都是流式处理的,没有一个大存储的需求,应该图像处理中DDR用的会比较多一些,但是一个这么…

SpringBoot05--axios网络请求

浏览器主动发送请求&#xff0c;服务器接收请求之后返回数据&#xff0c;通过vue进行数据绑定 请求成功&#xff0c;返回的数据会包装到response里面去&#xff08;返回成response的data属性&#xff09; 好的这边不太懂 在xxx.vue组件被创建和挂载&#xff08;渲染&#xff09…

在idea运行python文件

在idea运行python文件 如果在idea运行python文件而没有弹出run的选项&#xff0c;则点击File->Settings…->Plugins&#xff0c;在里面搜索python&#xff0c;如果没有显示则在Maketplace进行搜索&#xff0c; 接着Install&#xff0c;然后restart

C++ STL list

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C之 STL list介绍和模拟实现 ☂️<3>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<4>前言&#xff1a;上次我们详细的介绍了vector&#xff0c;今天我们继续来介绍…

某大厂笔试(小*的车站的最近距离)

有一个环形的公路&#xff0c;上面共有n站&#xff0c;现在给定了顺时针第i站到第i1站之间的距离&#xff08;特殊的&#xff0c;也给出了第n站到第1站的距离&#xff09;&#xff0c;小*想着沿着公路第x站走到第y站&#xff0c;她想知道最短的距离是多少&#xff1f; 输入描述…

无涯教程-Perl - print函数

描述 此函数将LIST中的表达式的值打印到当前的默认输出文件句柄或FILEHANDLE指定的句柄中。 如果设置,则$\变量将添加到LIST的末尾。 如果LIST为空,则打印$_中的值。 print接受一个值列表,列表中的每个元素都将被解释为一个表达式。 语法 以下是此函数的简单语法- print…

谷歌发布多平台应用开发神器:背靠 AI 编程神器 Codey,支持 React、Vue 等框架,还能代码补全

一、概述 8 月 8 日&#xff0c;谷歌宣布推出 AI 代码编辑器 IDX&#xff0c;旨在提供基于浏览器的人工智能开发环境&#xff0c;用于构建全栈网络和多平台应用程序。谷歌在创建 IDX 时并没有构建新的 IDE&#xff08;集成开发环境&#xff09;&#xff0c;而是使用 VS Code 作…

网络安全(黑客)自学路线/笔记

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…

代码随想录算法训练营第55天|动态规划part12|309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费、总结

代码随想录算法训练营第55天&#xff5c;动态规划part12&#xff5c;309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费、总结 309.最佳买卖股票时机含冷冻期 309.最佳买卖股票时机含冷冻期 思路&#xff1a; 区别在第i天持有股票的当天买入的情况&#xff0c…

【Kubernetes】神乎其技的K8s到底是什么,为什么被越来越多人使用

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前学习C/C、算法、Python、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&…

户外骨传导耳机推荐,盘点最适合户外佩戴的五款耳机

现在天气越来越暖和了&#xff0c;很多人选择外出徒步、越野或者骑行&#xff0c;在这些活动中往往都会搭配一个骨传导耳机&#xff0c;来让运动过程变得更加有趣。在选购骨传导耳机时&#xff0c;人们通常会考虑音质、舒适性、价格等因素&#xff0c;为了让大家选到更适合自己…

Kafka API与SpringBoot调用

文章目录 首先需要命令行创建一个名为cities的主题&#xff0c;并且创建该主题的订阅者。 1、使用Kafka原生API1.1、创建spring工程1.2、创建发布者1.3、对生产者的优化1.4、批量发送消息1.5、创建消费者组1.6 消费者同步手动提交1.7、消费者异步手动提交1.8、消费者同异步手动…

yolov5目标检测多线程Qt界面

上一篇文章&#xff1a;yolov5目标检测多线程C部署 V1 基本功能实现 mainwindow.h #pragma once#include <iostream>#include <QMainWindow> #include <QFileDialog> #include <QThread>#include <opencv2/opencv.hpp>#include "yolov5.…

phpspreadsheet excel导入导出

单个sheet页Excel2003版最大行数是65536行。Excel2007开始的版本最大行数是1048576行。Excel2003的最大列数是256列&#xff0c;2007以上版本是16384列。 xlswriter xlswriter - PHP 高性能 Excel 扩展&#xff0c;功能类似phpspreadsheet。它能够处理非常大的文件&#xff0…

056B R包ENMeval教程-基于R包ENMeval对MaxEnt模型优化调参和结果评价制图(更新)

056B-1 资料下载 056B-2 R包ENMeval在MaxEnt模型优化调参中的经典案例解读 056B-3 R软件和R包ENMeval工具包安装 056B-4 R软件和R包ENMeval安装报错解决办法 056B-5 环境数据格式要求和处理流程 056B-6 分布数据格式要求和处理流程 056B-7 基于R包ENMeval对MaxEnt模型优化…