java多线程(7.0)

news2025/7/10 1:32:32

目录

​编辑

定时器

定时器的使用 

三.定时器的实现 MyTimer

3.1 分析思路

1. 创建执行任务的类。

 2. 管理任务   

3. 执行任务

3.2 线程安全问题


 

定时器

定时器是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码.

定时器的使用 

标准库中的定时器 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule . schedule 包含两个参数.

第一个参数是继承timetask抽象类的类实例且内部重写了run方法(这里的匿名类隐式继承了TimerTask):指定即将要执行的任务代码(timetask实现了runable接口所以有run方法)

 第二个参数指定多长时间之后执行 (单位为毫秒).

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("hello");
   }
}, 3000);

执行schedule方法的时候,系统把要执行的任务放到timer对象中,与此同时timer对象里头自带一个线程叫做“扫描线程”,一旦时间到扫描线程就会执行刚才安排的任务,执行完所有任务后线程也不会销毁,会阻塞等待直到其他的任务被放到timer对象中再继续执行(就这么重复

三.定时器的实现 MyTimer

3.1 分析思路

对于定时器来说:

  1. 创建描述一个要执行的任务(任务内容 + 执行任务时间)的类
  2. 管理多个任务,通过一定的数据结构,把多个任务存起来
  3. 有专门的线程,执行这里的任务
1. 创建执行任务的类。

   我们在调用 schedule 时,传的是延迟时间 “delay” 值。但是,描述任务时,不太建议使用 delay

表示,最好使用 “绝对时间”(时间戳)来表示~~

public class MyTimerTask implements Comparable<MyTimerTask>{
    //此处这里的 time,通过毫秒时间戳,表示这个任务具体啥时候执行
    private long time;
    private Runnable runnable;
 
    public  MyTimerTask(Runnable runnable,long delay){
        this.time = System.currentTimeMillis() + delay;
        this.runnable = runnable;
    }
    public void run(){
        runnable.run();
    }
    public long getTime(){
        return time;
    }
 
    @Override
    public int compareTo(MyTimerTask o) {
        //比如,当前时间是 10:30,任务时间是 12:00,不应该执行
        //如果当前时间是 10:30,任务时间是 10:29,应该执行
        //谁减去谁,可以通过实验判断
        return (int) (this.time - o.time);
    }
}
 2. 管理任务   

使用 List 管理任务,不是一个好选择——因为后续执行列表中的任务时,就需要依次遍历每个元素;执行完毕后,还需要把对应的任务从 List 中删除掉。   
我们需要按照时间来执行这里的任务。只要能够确定所有任务中,时间最早的任务,判定它是否到该执行的时间即可。如果时间最早的任务还没到执行时间,其他任务更不可能到时间了。因此,我们使用堆数据结构(涉及到队列中的元素排序时,考虑堆)——PriorityQueue<MyTimerTask>(优先级队列管理元素时,需要有比较方法,才能排序存储。因此,在实现 MyTimerTask 类时,要继承 Comparable<MyTimerTask> 接口,重写 compareTo比较方法)

3. 执行任务

当创建 MyTimer 对象,调用无参构造方法时,便创建一个线程,循环执行从队列中取出任务的操作:取出队列中 “绝对时间” 最早的任务——如果当前时间 >= 此任务的时间(已经到达此任务的执行时间),便可调用run方法执行,执行完毕后从队列中删除; 如果当前时间 < 此任务的时间(没到此任务的执行时间),则继续执行循环。(所以,在实现 MyTimerTask 类时,要有 run方法 和 getTime方法)

   除此之外 ,还需要有 schedule 方法添加任务。

import java.util.PriorityQueue;
 
 
public class MyTimer {
    private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    
    public MyTimer() {
        Thread t = new Thread(() -> {
             while (true){
                   if(queue.isEmpty()){ 
                      continue;
                   }
                   MyTimerTask task = queue.peek();
                   //判断是否满足执行条件
                   if (System.currentTimeMillis() >= task.getTime()) {
                       task.run();
                       //执行完后,便从队列中删除
                       queue.poll();
                   }else{
                       continue;
                   }
              }
        });
        t.start();
 
    }
 
    public void schedule(Runnable runnable, long delay) {
         MyTimerTask task = new MyTimerTask(runnable, delay);
         queue.offer(task);
        
    }
}
3.2 线程安全问题

   当前这个代码,是没有考虑线程安全问题的。

   PriorityQueue 这个类自身,是非线程安全的,并且又是多个线程来进行操作,一定存在线程安全问题的风险。因此,要在涉及队列相关操作的地方加锁。(让删的时候不能进行加入操作,加的时候不进行删除操作)

import java.util.PriorityQueue;
 
 
public class MyTimer {
    private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private final Object locker = new Object();
 
    public MyTimer() {
        Thread t = new Thread(() -> {
             while (true){
                synchronized(locker){
                    if(queue.isEmpty()){ 
                      continue;
                   }
                   MyTimerTask task = queue.peek();
                   if (System.currentTimeMillis() >= task.getTime()) {
                       task.run();
                       queue.poll();
                   }else{
                       continue;
                   }
                }
            }
        });
        t.start();
 
    }
 
    public void schedule(Runnable runnable, long delay) {
        synchronized(locker){
           MyTimerTask task = new MyTimerTask(runnable, delay);
           queue.offer(task);
        }
    }
}

 但是,加完锁以后,又出现了线程安全问题。 

1)初始情况下,如果队列中,没有任何元素。此时,就会在短时间内执行大量循环,这样的执行是没有意义的,导致“线程饿死”。

因此,我们需要添加 wait 和 notify 机制:队列为空时,进行等待;添加任务时,就唤醒线程。

2)假设队列中,已经包含元素了,并且当前时间是 10:45,任务时间 12:00(类似于,我定了12:00的闹钟,现在是 10:45)。在判断任务是否满足执行条件时,不满足就会一直循环(相当于每隔一会儿就看一眼闹钟),这样无意义的执行就一直占用着cpu资源,导致 “线程饿死”。

因此,我们需要添加一个有等待期限的 wait(等待 1h15min 就会执行),当到达任务执行时间,wait 就结束了。如果在等待过程中,又再次调用 schedule 方法,也会唤醒这里的 wait,进行新一轮的判断。

 线程安全版:

import java.util.PriorityQueue;
 
public class MyTimer {
    private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private final Object locker = new Object();
 
    public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            //如果还没添加任务,会不断循环执行判断,出现线程饿死。
                            //continue;
                            //因此,使用wait等待,当添加任务后唤醒
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        if (System.currentTimeMillis() >= task.getTime()) {
                            task.run();
                            queue.poll();
                        } else {
                            //如果还没到任务执行时间,依旧不断循环判断,出现线程饿死。
                            //continue;
                            //因此,使用有等待期限的 wait,计算执行的时间与当前时间的差值
                            //当添加新的任务后,wait 被唤醒,再进行新的判断
                            locker.wait(task.getTime() - System.currentTimeMillis());
                        }
                    }
 
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t.start();
    }
 
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker){
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            // 唤醒 wait
            locker.notify();
        }
    }
}

* 能否将第二处的 wait 改为 sleep 呢? ——不能!!

 不应该使用sleep,可能存在以下情况:
     1)在 sleep 阻塞1h15min 的过程中,新来了一个时间更早的任务,比如 11:30 要执行。如果使用 wait ,每次新来的任务,都会把 wait 唤醒,重新设定 wait 的等待时间。而 sleep 不会被唤醒,依旧在阻塞着.....
     2)sleep 休眠的时候,不会释放锁。因此,在休眠的时候就是“抱着锁”,其他人想拿锁就拿不到了。也就是说你休眠的时候,就不能进行增加线程

 * PriorityQueue 是线程不安全的类,能否使用 PriorityBlockingQueue 线程安全的阻塞队列呢?——不能!!    
如果使用线程安全的队列,会导致代码中从 一把锁 变成 两把锁,很容易出现死锁的情况,比如持有和请求的情况(并非100%一定出现,但是需要程序员精心控制加锁顺序,使得编写代码的复杂度提高了。如果通篇代码 只有一把锁,就能更容易地解决问题)

 

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

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

相关文章

Long类型封装Json传输时精度丢失问题

在信息做传输时&#xff0c;经常会使用到类型转换&#xff0c;这个时候因为一些问题会导致精度的丢失。在支付业务中这种问题更为致命。 这里我主动生成一个支付订单并将相关信息使用base64编码为一个二维码返回给前端进行支付&#xff0c;前端进行支付时我通过回调方法发现回调…

《从GPT崛起,看AI重塑世界》

《从GPT崛起,看AI重塑世界》 GPT 诞生:AI 领域的震撼弹 2022 年 11 月 30 日,OpenAI 发布了一款名为 ChatGPT 的人工智能聊天机器人程序,宛如一颗重磅炸弹投入了平静的湖面,迅速在全球范围内引发了轩然大波,成为了科技领域乃至大众舆论场中最热门的话题之一。一时间,无…

系统架构-安全架构设计

概述 对于信息系统来说&#xff0c;威胁有&#xff1a;物理环境&#xff08;最基础&#xff09;、通信链路、网络系统、操作系统、应用系统、管理系统 物理安全&#xff1a;系统所用设备的威胁&#xff0c;如自然灾害、电源故障通信链路安全&#xff1a;在传输线路上安装窃听…

鼠标指定范围内随机点击

鼠标指定范围内随机点击 点赞神器 将鼠标移动到相应位置后按F5 F6键&#xff0c;设置点击范围&#xff0c; F8开始&#xff0c;ESC中止。 有些直播有点赞限制&#xff0c;例如某音&#xff0c;每小时限制3千次&#xff0c;可以设置1200毫秒&#xff0c;3000次。 软件截图&#…

HashSet 概述

1. HashSet 概述 HashSet 是 Java 集合框架中 Set 接口的一个实现类&#xff0c;它存储唯一元素&#xff0c;即集合中不会有重复的元素。HashSet 基于哈希表&#xff08;实际上是 HashMap 实例&#xff09;来实现&#xff0c;不保证元素的顺序&#xff0c;并且允许存储 null 元…

遥测终端机,推动灌区流量监测向数据驱动跃迁

灌区范围那么大&#xff0c;每一滴水怎么流都关系到粮食够不够吃&#xff0c;还有生态能不能平衡。过去靠人工巡查、测量&#xff0c;就像拿着算盘想算明白大数据&#xff0c;根本满足不了现在水利管理的高要求。遥测终端机一出现&#xff0c;就像给灌区流量监测安上了智能感知…

蓝耘平台介绍:算力赋能AI创新的智算云平台

一、蓝耘平台是什么 蓝耘智算云&#xff08;LY Cloud&#xff09;是蓝耘科技打造的现代化GPU算力云服务平台&#xff0c;深度整合自研DS满血版大模型技术与分布式算力调度能力&#xff0c;形成"模型算力"双轮驱动的技术生态。平台核心优势如下&#xff1a; 平台定位…

QtDesigner中Button控件详解

一&#xff1a;Button控件 关于Button控件的主要作用就是作为触发开关&#xff0c;通过点击事件&#xff08;click&#xff09;执行代码逻辑&#xff0c;或者作为功能入口&#xff0c;跳转到其他界面或模块。 二&#xff1a;常见属性与配置 ①Button的enabled&#xff0c;大…

Flink 源码编译

打包命令 打包整个项目 mvn clean package -DskipTests -Drat.skiptrue打包单个模块 mvn clean package -DskipTests -Drat.skiptrue -pl flink-dist如果该模块依赖其他模块&#xff0c;可能需要先将其他模块 install 到本地&#xff0c;如果依赖的模块的源代码有修改&#…

docker的安装和简单使用(ubuntu环境)

环境准备 这里用的是linux的环境&#xff0c;如果没有云服务器的话&#xff0c;就是用虚拟环境吧。 虚拟环境的安装参考&#xff1a;vmware17的安装 linux镜像的安装 docker安装 我使用的是ubuntu&#xff0c;使用以下命令&#xff1a; 更新本地软件包索引 sudo apt u…

EasyRTC音视频实时通话在线教育解决方案:打造沉浸式互动教学新体验

一、方案概述 EasyRTC是一款基于WebRTC技术的实时音视频通信平台&#xff0c;为在线教育行业提供了高效、稳定、低延迟的互动教学解决方案。本方案将EasyRTC技术深度整合到在线教育场景中&#xff0c;实现师生间的实时音视频互动等核心功能&#xff0c;打造沉浸式的远程学习体…

【分布式系统中的“瑞士军刀”_ Zookeeper】一、Zookeeper 快速入门和核心概念

在分布式系统的复杂世界里&#xff0c;协调与同步是确保系统稳定运行的关键所在。Zookeeper 作为分布式协调服务的 “瑞士军刀”&#xff0c;为众多分布式项目提供了高效、可靠的协调解决方案。无论是在分布式锁的实现、配置管理&#xff0c;还是在服务注册与发现等场景中&…

Electron从入门到入门

项目说明 项目地址 项目地址&#xff1a;https://gitee.com/ruirui-study/electron-demo 本项目为示例项目&#xff0c;代码注释非常清晰&#xff0c;给大家当做入门项目吧。 其实很多东西都可以在我这基础上添加或修改、市面上有些已开源的项目&#xff0c;但是太臃肿了&am…

优化提示词方面可以使用的数学方法理论:信息熵,概率论 ,最优化理论

优化提示词方面可以使用的数学方法理论:信息熵,概率论 ,最优化理论 目录 优化提示词方面可以使用的数学方法理论:信息熵,概率论 ,最优化理论信息论信息熵明确问题主题提供具体细节限定回答方向规范语言表达概率论最优化理论信息论 原理:信息论中的熵可以衡量信息的不确定性。…

腾讯一面面经:总结一下

1. Java 中的 和 equals 有什么区别&#xff1f;比较对象时使用哪一个 1. 操作符&#xff1a; 用于比较对象的内存地址&#xff08;引用是否相同&#xff09;。 对于基本数据类型、 比较的是值。&#xff08;8种基本数据类型&#xff09;对于引用数据类型、 比较的是两个引…

Golang | 倒排索引

文章目录 倒排索引的设计倒排索引v0版实现 倒排索引的设计 通用搜索引擎 v.s. 垂直搜索引擎&#xff1a; 通用搜索引擎&#xff1a;什么都可以搜索&#xff0c;更加智能化垂直搜索引擎&#xff1a;只能搜自家数据库里面的内容&#xff0c;一般都带着搜索条件&#xff0c;搜索一…

大模型驱动智能服务变革:从全流程赋能到行业纵深落地

大模型技术的快速发展&#xff0c;正深刻改变着人工智能的研发与应用模式。作为"软硬协同、开箱即用"的智能化基础设施&#xff0c;大模型一体机通过整合计算硬件、部署平台和预置模型&#xff0c;重构了传统AI部署方式&#xff0c;成为推动AI普惠化和行业落地的重要…

【初识Trae】字节跳动推出的下一代AI原生IDE,重新定义智能编程

​ 初识官网文档 从官网可以看到有两个大标签页&#xff0c;即Trae IDE CN和Trae插件&#xff0c;这就说明Trae在发布Trae IDE的同时考虑到对主流IDE的插件支持&#xff0c;这一点非常有心&#xff0c;但是我估测Trae IDE的体验更好&#xff08;就是AI IDE出生&#xff0c;毕…

装备制造企业选型:什么样的项目管理系统最合适?

个性化定制需求日益增加、项目周期长、供应链协同复杂、成本控制难度大、以及设计、生产、安装、售后等环节协同不畅。这些挑战使得装备制造企业在传统的管理方式捉襟见肘&#xff0c;迫切需要一套高效、智能的项目管理系统来提升运营效率和盈利能力。 那么&#xff0c;对于装…

QT多元素控件及其属性

Qt中提供的多元素控件有&#xff1a; QListWidget QListView QTableWidget QTableView QTreeWidget QTreeView widget和view多元素控件的区别&#xff1a; view是更底层的实现&#xff0c;widget是基于view封装而来&#xff0c;view是MVC结构的一种典型实现 MVC结构&am…