Java多线程:定时器

在这里插入图片描述

  • 👑专栏内容:Java
  • ⛪个人主页:子夜的星的主页
  • 💕座右铭:前路未远,步履不停

目录

  • 一、`Timer`类
  • 二、手动实现定时器
    • 1、实现逻辑
    • 2、问题描述
      • 2.1、问题一:线程安全问题
      • 2.2、问题二:使用 `sleep` 休眠
      • 2.3、问题三:没有比较规则
    • 3、代码实现


一、Timer

java.util.Timer类是 Java 中内置的定时器类,用于在指定的时间或时间间隔执行任务。
在这里插入图片描述

方法名参数描述
Timer()创建一个计时器并启动该计时器
schedule(TimerTask task, long delay)task: 要执行的任务, delay: 延迟时间(毫秒)安排在指定延迟后执行一次任务
schedule(TimerTask task, long delay, long period)task: 要执行的任务, delay: 延迟时间(毫秒), period: 重复执行的时间间隔(毫秒)安排在指定延迟后开始重复执行任务,每次执行之间间隔指定的时间 period
scheduleAtFixedRate(TimerTask task, long delay, long period)task: 要执行的任务, delay: 延迟时间(毫秒), period: 重复执行的时间间隔(毫秒)安排在指定延迟后开始重复执行任务,每次执行之间间隔固定的时间 period
cancel()中止该计时器,并放弃所有已安排的任务,对当前正在执行的任务没有影响
purge()将所有已取消的任务移除,一般用来释放内存空间

定时器示例:

import java.util.Timer;
import java.util.TimerTask;

// 定时器的使用
public class Demo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        //给 timer中注册的这个任务,不上在调用schedule的线程中执行的,而是通过Timer内部的线程来负责执行的。
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时任务!");
            }
        },3000);
        System.out.println("程序开始运行!");
    }
}

import java.util.Timer;
import java.util.TimerTask;

// 定时器的使用
public class Demo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        //给 timer中注册的这个任务,不上在调用schedule的线程中执行的,而是通过Timer内部的线程来负责执行的。
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时任务 1!");
            }
        },1000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时任务 2!");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时任务 3!");
            }
        },3000);

        System.out.println("程序开始运行!");
    }
}

注意事项:

  • Timer类是非线程安全的,因此在多线程环境下使用时需要注意同步问题。
  • Timer类在执行任务时可能会抛出异常,因此需要在任务中进行异常处理。
  • Timer类在JVM退出时不会自动销毁,因此需要在程序退出时显式销毁计时器。

二、手动实现定时器

1、实现逻辑

我们手动创建一个 TimerTask 类来描述一个定时任务,该类包含以下两个方面:

  • 任务: 要执行的任务代码,可以是任何Runnable实例。
  • 实际执行时间: 使用时间戳表示任务的执行时间。

数据结构选择:

为了高效地管理多个定时任务,我们需要选择合适的数据结构来组织它们。使用普通List(数组或链表)会存在以下问题:

  • 无法快速确定何时执行特定任务,需要遍历整个List进行比较。
  • 当任务数量巨大时,遍历操作会消耗大量时间,降低效率。

优先级队列:

使用优先级队列可以有效解决上述问题。优先级队列是一种根据元素的优先级排序的数据结构,队首元素始终是优先级最高的元素。

实现原理:

  1. 将所有任务按照实际执行时间排序,并插入到优先级队列中。
  2. 创建一个守护线程不断扫描队列:
    • 如果队首元素的实际执行时间未到,则等待一段时间再进行下一次扫描。
    • 如果队首元素的实际执行时间已到,则取出该元素并执行其任务。

优势:

  • 能够快速确定何时执行特定任务,只需检查队首元素即可。
  • 即使任务数量巨大,也能保持高效的执行效率,因为只需要关注队首元素。
// 描述定时器的一个任务
class MyTimeTask{
    //啥时候执行,毫秒时间戳
    private long time;
    //任务是什么
    private Runnable runnable;

    public long getTime() {
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public MyTimeTask(Runnable runnable, long delay){
        //delay 是一个相对时间差,形如3000这种
        //构造time要根据当前系统的时间和dalay构造
        time = System.currentTimeMillis() + delay;
        this.runnable = runnable;

    }
}
class Mytimer{
    //优先级队列保存上述的N个任务
    private PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
    //定时器的核心方法:要执行的任务添加到队列中
    private void schedule(Runnable runnable,long delay){
        MyTimeTask task = new MyTimeTask(runnable,delay);
        queue.offer(task);
    }
    //MyTimer 中还需要构造一个扫描线程,一方面负责监控队首元素是否执行,一方面当任务到点后
    // 调用这个Runnable的run方法完成任务
    private Mytimer(){
        //扫描线程
        Thread t = new Thread(()->{
            while(true){
                try {
                if(queue.isEmpty()){
                    //当前队列为空,不应该去取元素,直接跳过
                    continue;
                }
                MyTimeTask task = queue.peek();
                long curTime = System.currentTimeMillis();
                if(curTime>= task.getTime()){
                    //假设当前时间是 14.01,任务时间是 14.00,就是需要执行任务
                    queue.poll();
                    task.getRunnable().run();
                }else{
                    //让当前扫描线程休眠,按照时间差进行休眠。
                        Thread.sleep(task.getTime()-curTime);
                    }
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

2、问题描述

2.1、问题一:线程安全问题

PriorityQueue是非线程安全的,在多线程环境下使用可能会导致数据错乱。

解决方案:

  • 使用 concurrent 包中的 PriorityQueue 实现。
  • 使用 synchronized 关键字对 PriorityQueue 进行加锁。

2.2、问题二:使用 sleep 休眠

sleep 方法会阻塞当前线程,导致其他线程无法执行。

解决方案:

  • 使用 wait 方法代替 sleep 方法,使线程进入等待状态,并释放 CPU 资源。
  • 使用 notifynotifyAll 方法唤醒等待线程。

2.3、问题三:没有比较规则

PriorityQueue 要求放入其中的元素必须实现 Comparable 接口或提供 Comparator 比较器,以便根据元素的优先级进行排序。

解决方案:

  • TimerTask 类中实现 Comparable 接口,重写 compareTo 方法定义比较规则。
  • 在创建 PriorityQueue 时传入自定义的 Comparator 比较器。

3、代码实现

package thread;

import java.util.PriorityQueue;
import java.util.Timer;
import java.util.TimerTask;

// 描述定时器的一个任务
class MyTimeTask implements Comparable<MyTimeTask>{
    //啥时候执行,毫秒时间戳
    private long time;
    //任务是什么
    private Runnable runnable;

    public long getTime() {
        return time;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    @Override
    public int compareTo(MyTimeTask o) {
        // 认为时间小的优先级高,时间最小的放到队首。
        return (int)(this.time - o.time);
    }

    public MyTimeTask(Runnable runnable, long delay){
        //delay 是一个相对时间差,形如3000这种
        //构造time要根据当前系统的时间和dalay构造
        time = System.currentTimeMillis() + delay;
        this.runnable = runnable;

    }
}
class Mytimer{
    private  Object locker = new Object();
    //优先级队列保存上述的N个任务
    private PriorityQueue<MyTimeTask> queue = new PriorityQueue<>();
    //定时器的核心方法:要执行的任务添加到队列中
    void schedule(Runnable runnable, long delay){
        synchronized (locker){
            MyTimeTask task = new MyTimeTask(runnable,delay);
            queue.offer(task);
            //每次来新的任务,都唤醒一下扫描线程。
            locker.notify();
        }
    }
    //MyTimer 中还需要构造一个扫描线程,一方面负责监控队首元素是否执行,一方面当任务到点后
    // 调用这个Runnable的run方法完成任务
    public Mytimer(){
        //扫描线程
        Thread t = new Thread(()->{
            while(true){
                try {
                    synchronized (locker){
                        while(queue.isEmpty()){
                            //当前队列为空,不应该去取元素,直接跳过
                            //此处使用wait等待更合适,使用continue就会让while循环运行飞快,陷入高频占用CPU的状态(盲等)。
                            locker.wait();
                        }
                        MyTimeTask task = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if(curTime>= task.getTime()){
                            //假设当前时间是 14.01,任务时间是 14.00,就是需要执行任务
                            queue.poll();
                            task.getRunnable().run();
                        }else{
                            //让当前扫描线程休眠,按照时间差进行休眠。
                            //Thread.sleep(task.getTime()-curTime);
                            locker.wait(task.getTime()-curTime);
                        }
                    }
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}
public class Demo {
    // 一个定时器
    public static void main(String[] args) {
        Mytimer timer = new Mytimer();
        //给 timer中注册的这个任务,不上在调用schedule的线程中执行的,而是通过Timer内部的线程来负责执行的。
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行定时任务 1!");
            }
        },1000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行定时任务 2!");
            }
        },2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行定时任务 3!");
            }
        },3000);

        System.out.println("程序开始运行!");
    }
    
}

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

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

相关文章

[C# WPF] DataGrid选中行或选中单元格的背景和字体颜色修改

问题描述 WPF中DataGrid的选中行或选中者单元格&#xff0c;在焦点失去后&#xff0c;颜色会很淡&#xff0c;很不明显&#xff0c;不容易区分。 解决方法 在失去焦点的情况下&#xff0c;如何设置行或单元格与选中的时候颜色一样&#xff1f; <DataGrid.Resources>&…

滑块识别验证

滑块识别 1. 获取图片 测试网站&#xff1a;https://www.geetest.com/adaptive-captcha-demo 2. 点击滑块拼图并开始验证 # 1.打开首页 driver.get(https://www.geetest.com/adaptive-captcha-demo)# 2.点击【滑动拼图验证】 tag WebDriverWait(driver, 30, 0.5).until(la…

利用YOLOv8 pose estimation 进行 人的 头部等马赛克

文章大纲 马赛克几种OpenCV 实现马赛克的方法高斯模糊pose estimation 定位并模糊:三角形的外接圆与膨胀系数实现实现代码实现效果参考文献与学习路径之前写过一个文章记录,怎么对人进行目标检测后打码,但是人脸识别有个问题是,很多人的背影,或者侧面无法识别出来人脸,那…

Golang GC 介绍

文章目录 0.前言1.发展史2.并发三色标记清除和混合写屏障2.1 三色标记2.2 并发标记问题2.3 屏障机制Dijkstra 插入写屏障Yuasa 删除写屏障混合写屏障 3.GC 过程4.GC 触发时机5.哪里记录了对象的三色状态&#xff1f;6.如何观察 GC&#xff1f;方式1&#xff1a;GODEBUGgctrace1…

算法学习——LeetCode力扣二叉树篇1

算法学习——LeetCode力扣二叉树篇1 144. 二叉树的前序遍历 144. 二叉树的前序遍历 - 力扣&#xff08;LeetCode&#xff09; 描述 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 示例 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&a…

大水仙花数求解

输入位数&#xff0c;求解水仙花数。暴力求解&#xff0c;位数如果太多&#xff0c;会超时。 思路&#xff1a; &#xff08;1&#xff09;11333355和33331155看上去是不一样的两个数&#xff0c;但是它们又一样&#xff0c;因为相同数字出现的次数一样。 &#xff08;2&…

深度学习图像分类相关概念简析+个人举例3(CNN相关补充,附详细举例代码1)

【1】激活函数&#xff08;Activation Function&#xff09;&#xff1a;在深度学习&#xff08;CNN&#xff09;中&#xff0c;激活函数用于引入非线性性质&#xff0c;帮助模型学习复杂的关系。常见的激活函数有ReLU、Sigmoid和Tanh等。 &#xff08;1&#xff09;ReLU激活函…

2万字曝光:华尔街疯狂抢购比特币背后

作者/来源&#xff1a;Mark Goodwin and whitney Webb BitcoinMagazine 编译&#xff1a;秦晋 全文&#xff1a;19000余字 在最近比特币ETF获得批准之后&#xff0c;贝莱德的拉里-芬克透露&#xff0c;很快所有东西都将被「ETF化」与代币化&#xff0c;不仅威胁到现有的资产和商…

详细介绍Python网络编程模块

根据前面对网络分层棋型的介绍&#xff0c;我们知道实际的网络模型大致分为四层&#xff0c;这四层各有对应的网络协议提供支持&#xff0c; 网络层协议主要是 IP&#xff0c;它是所有互联网协议的基础&#xff0c;其中 ICMP&#xff08;Internet Control Message Protocol&…

JAVA设计模式之策略模式详解

策略模式 1 策略模式概述 策略模式(strategy pattern)的原始定义是&#xff1a;定义一系列算法&#xff0c;将每一个算法封装起来&#xff0c;并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。 其实我们在现实生活中常常遇到实现某种目标存在多种策略…

Netty应用(一) 之 NIO概念 基本编程

目录 第一章 概念引入 1.分布式概念引入 第二章 Netty基础 - NIO 1.引言 1.1 什么是Netty&#xff1f; 1.2 为什么要学习Netty&#xff1f; 2.NIO编程 2.1 传统网络通信中开发方式及问题&#xff08;BIO&#xff09; 2.1.1 多线程版网络编程 2.1.2 线程池版的网络编程…

6.JavaScript中赋值运算符,自增运算符,比较运算符,逻辑运算符

赋值运算符 就是简单的加减乘除&#xff0c;没啥可说的这里直接上代码比较好 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><…

一文彻底搞懂Kafka如何保证消息不丢失

文章目录 1. kafka 架构2. producer端是如何保证数据不丢失的2.1 同步发送2.2 异步发送2.3 批量发送 3. consumer端是如何保证数据不丢失的3.1 手动提交3.2 幂等性消费 4. broker端是如何保证数据不丢失的4.1 副本机制4.2 ISR机制4.3 刷盘机制 1. kafka 架构 Producer&#xff…

redis的主从配置模拟(一主双从)

目录 先来给大家扩展机道面试官经常会问到关于redis的题 一、redis有哪些好处 二、redis相比memcached有哪些优势 三、redis常见性能问题和解决方案 四、redis集群的工作原理 五、redis主从的原理 redis的主从配置模拟&#xff08;一主双从&#xff09; 一、准备环境 …

二级建造师试题答案?学生党都在用的6款搜题工具来了 #学习方法#学习方法#微信

作为大学生&#xff0c;我们应该善于利用各种学习工具&#xff0c;提高学习效率和质量。 1.灵兔搜题 这是一个公众号 包含大学网课、课后教材、选修课、mooc慕课及各类职业资格证、学历提升考试、公务员考试等常见题库。 下方附上一些测试的试题及答案 1、Uri主要由三部分组…

spark sql上线前的调试工作实现

背景 每个公司应该都有大数据的平台的吧&#xff0c;平台的作用就是可以在上面执行各种spark sql以及定时任务&#xff0c;不过一般来说&#xff0c;由于这些spark sql的上线不经过测试&#xff0c;所以可能会影响到生产的数据&#xff0c;这种情况下大数据平台提供一个上线前…

Blazor Wasm Google 登录

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor SSR/WASM IDS/OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasmBlazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端…

Docker 有哪些常见的用途?

Docker 是一种容器化技术&#xff0c;它允许应用程序在不同的环境之间具有一致的运行环境。这使得 Docker 在开发和运维领域中非常受欢迎&#xff0c;因为它简化了应用程序的部署和管理。以下是 Docker 的一些常见用途&#xff1a; 快速部署应用程序 Docker 允许开发人员和运…

Mysql Day04

mysql体系结构 连接层服务层引擎层&#xff08;索引&#xff09;存储层 存储引擎 存储引擎是基于表建立的&#xff0c;默认是innoDB show create table tb; 查看当前数据库支持的存储引擎 show engines; InnoDB 特点 DML&#xff08;数据增删改&#xff09;遵循ACID模…

c++之说_11|自定义类型 enum(枚举)与enumclass (c11新枚举)

至于枚举 会用就行 至少目前我感觉没什么太多问题 enum 被称为无作用域枚举 &#xff0c; enumclass / enumstruct 被称为有作用域枚举 看到了吧 语法规则 和 struct 差不多 只不过枚举成员 只是一个标志 它本质是数值 从上到下 下面的数根据上面的数 加 1 也可以直接…