定时器详解

定时器:Timer类

常用方法方法:

1.schedule(TimeTask timetask,long delay,(long period)):

TimeTask:实现了Runnable类,实现时需要重写run方法

delay:表示延迟多少(decay)后开始执行任务,单位是毫秒,这个参数也可以是日期(Date)

period:周期时间,表示定时器循环执行任务之间的间隔时间,时间是毫秒

如果没有period参数,那么就只执行一次任务内容

2.scheduleAtFixedRate(TimeTask timetask,long delay,long period):

参数和schedule的参数相同,其作用为定时器设置循环执行的内容,第一次执行内容的延迟时间,循环的周期时间。可以看出schedule方法的功能其实已经包括这个方法了

3.cancel():关闭计时器


schedule(TimeTask timetask,long delay)

public static void main(String[] args) {
        System.out.println("任务三秒后开启");
        
        Timer t = new Timer();
        //定时执行任务 表示几秒后执行run方法里面的内容
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务开启");
            }
        },3000);
    }


schedule(TimeTask timetask,long delay,long period):

 public static void main(String[] args) {
        System.out.println("任务三秒后开启");

        Timer t = new Timer();
        //定时执行任务 表示几秒后执行run方法里面的内容
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("开始以1秒的时间间隔循环执行任务");
            }
        },2000,1000);
    }


scheduleAtFixedRate(TimeTask timetask,long delay,long period):

 public static void main(String[] args) {
        System.out.println("任务两秒后开启");

        Timer t = new Timer();
        //定时执行任务 表示几秒后执行run方法里面的内容
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务开启");
            }
        },2000);

        t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("开始循环任务(间隔1s)");
            }
        },2000,1000);
    }


指定定时器执行固定个任务后结束
public static void main(String[] args) {
        Timer t = new Timer();
        int timeCount = 5;
        long delay = 1000;
        long period = 1000;

        t.schedule(new TimerTask() {
            int count = 1;
            @Override
            public void run() {
                if(count >= timeCount){
                    t.cancel();
                }
                System.out.println("执行任务:"+count);
                count++;
            }
        },delay,period);
    }


模拟实现定时器

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

class Task implements Comparable<Task>{
    //这个类用来描述任务的
    private Runnable runnable;

    //执行的任务时间
    private long time; //time + System.currentTimeMillis()

    public Task(Runnable runnable,long time){
        this.runnable = runnable;
        this.time = time;
    }

    public long getTime(){
        return time ;
    }

    public void run() {
        runnable.run();
    }

    //比较规则 执行时间在前的先执行
    @Override
    public int compareTo(Task o) {
        return (int)(this.time - o.time);
    }
}
public class MyTimer{
    //一个阻塞队列
    private BlockingQueue<Task> queue = new PriorityBlockingQueue<>();

    //扫描线程
    private Thread t = null;

    public MyTimer(){
            t = new Thread(() -> {
                while(true) {
                    try {
                        Task task = queue.take();
                        if (task.getTime() > System.currentTimeMillis()) {
                            //还没到时间
                            queue.put(task);
                        } else {
                            //执行任务
                            task.run();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }

    public void schedule(Runnable runnable,long time) throws InterruptedException {
        //在这一步加System.currentTimeMillis() 不能在getTime方法中加,不然就会一直大于System.currentTimeMillis()
        Task task = new Task(runnable, time+System.currentTimeMillis());
        queue.put(task);
    }

    public static void main(String[] args) throws InterruptedException {
        MyTimer m = new MyTimer();
        m.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是任务1");
            }
        },2000);

        m.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是任务2");
            }
        },1000);
    }
}


问题:
1.忙等

假设此时时间是上午9点,第一个任务的时间是上午10点,那么在这一个小时内,程序会一直重复执行一个代码:

 Task task = queue.take();
 if (task.getTime() > System.currentTimeMillis()) {
     //还没到时间
     queue.put(task);
 }

这行代码可能会循环个非常多亿次,且别忘了,优先级队列的底层是用堆实现的,每当我们取出一个元素又出现插入时,根据堆的调整,此元素又会在堆顶,而每一层调整都是有开销的,故这一时间段的开销是重复且多余的,所以我们就等一次,等堆顶任务执行时间与当前时间的差值就可,修改代码:

 Task task = queue.take();
 if (task.getTime() > System.currentTimeMillis()){
     //还没到时间
     queue.put(task);
     Thread.sleep(task.getTime() - System.currentTimeMillis());

大伙们是不是觉得此时的代码就已经完美了?!!

然而并不是! 我们并不能使用sleep休眠来休眠两时间差

试想一下,如果咱们休眠的时候突然插进来了一个上午9.30执行的任务,那么这个任务就不会被执行到!故使用带参版本的wait()方法才是最好的选择

再次修改代码:

 if (task.getTime() > System.currentTimeMillis()){
     //还没到时间
     queue.put(task);
     synchronized (this){
        this.wait(task.getTime() - System.currentTimeMillis());
     }


 public void schedule(Runnable runnable,long time) throws InterruptedException {
        Task task = new Task(runnable, time+System.currentTimeMillis());
        queue.put(task);
        //唤醒t线程里的wait操作
        this.notify();
     }

这个时候代码看起来是不是似乎万无一失了已经!

but,我们来考虑一个极端极端极端极端的情况


2.极端情况
 if (task.getTime() > System.currentTimeMillis()){
     //还没到时间
     queue.put(task);
     synchronized (this){
        this.wait(task.getTime() - System.currentTimeMillis());
     }

如果线程1在执行到queue.put(task)时,恰好被调度走了,此时另一个线程调用schedule方法,且如果当线程2的任务时间小于线程1的任务时间时,此时线程2的任务就不会执行到。因为线程2的notify没作用,线程1都还没有执行到wait方法,但线程1重新执行时,此时的时间差已经是固定了的,但是这个时间差要大于线程2任务执行的时间,故线程2就会被“极端”地错过


完美代码

加大锁的力度:造成此极端情况的原因即为:take和wait是多步操作,非原子性

public MyTimer(){
            t = new Thread(() -> {
                while(true) {
                    synchronized (this){
                        try {
                            Task task = queue.take();
                            if (task.getTime() > System.currentTimeMillis()) {
                                //还没到时间
                                queue.put(task);
                                this.wait(task.getTime() - System.currentTimeMillis());
                            } else {
                                //执行任务
                                task.run();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            t.start();
        }

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

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

相关文章

密码学 | 椭圆曲线密码学 ECC 入门(四)

目录 正文 1 曲线方程 2 点的运算 3 求解过程 4 补充&#xff1a;有限域 ⚠️ 知乎&#xff1a;【密码专栏】动手计算双线性对&#xff08;中&#xff09; - 知乎 ⚠️ 写在前面&#xff1a;本文属搬运博客&#xff0c;自己留着学习。注意&#xff0c;这篇博客与前三…

LeetCode in Python 200. Number of islands (岛屿数量)

岛屿数量既可以用深度优先搜索也可以用广度优先搜索解决&#xff0c;本文给出两种方法的代码实现。 示例&#xff1a; 图1 岛屿数量输入输出示意图 方法一&#xff1a;广度优先搜索(bfs) 代码&#xff1a; class Solution:def numIslands(self, grid):if not grid:return 0…

【WSL报错】执行:wsl --list --online;错误:0x80072ee7

【WSL报错】执行:wsl --list --online&#xff1b;错误:0x80072ee7 问题情况解决方法详细过程 问题情况 C:\Users\17569>wsl --list --online 错误: 0x80072ee7 解决方法 开系统代理&#xff0c;到外网即可修复&#xff01;&#xff01;&#xff01;&#xff01;&#x…

Yolov8项目实践——基于yolov8与OpenCV实现目标物体运动热力图

概述 在数据驱动和定位的世界中&#xff0c;对数据进行解释、可视化和决策的能力变得日益重要。这表明&#xff0c;使用正确的工具和技术可能是项目成功的关键。在计算机视觉领域&#xff0c;存在许多技术来解释从视频&#xff08;包括录像、流媒体或实时视频&#xff09;中获…

「 网络安全常用术语解读 」软件成分分析SCA详解:从发展背景到技术原理再到业界常用检测工具推荐

软件成分分析&#xff08;Software Composition Analysis&#xff0c;SCA&#xff09;是一种用于识别和分析软件内部组件及其关系的技术&#xff0c;旨在帮助开发人员更好地了解和管理其软件的构建过程&#xff0c;同时可帮助安全人员揭秘软件内部结构的神秘面纱。SCA技术的发展…

递归、搜索与回溯算法:回溯,决策树

回溯算法是⼀种经典的递归算法&#xff0c;通常⽤于解决组合问题、排列问题和搜索问题等。 回溯算法的基本思想&#xff1a;从⼀个初始状态开始&#xff0c;按照⼀定的规则向前搜索&#xff0c;当搜索到某个状态⽆法前进时&#xff0c;回退到前⼀个状态&#xff0c;再按照其他…

【计算机组成原理】运算方法和运算器

数据与文字的表示方法 1. 数据格式1.1 定点数表示方法1.1.1 定点小数1.1.2 定点整数 1.2 浮点数表示方法1.2.1 浮点数表示1.2.2 浮点数的规格化1.2.2.1 尾数为原码表示的规格化1.2.2.2 尾数为补码表示的规格化 1.2.3 IEEE754标准⭐ 1.3 十进制数串的表示方法1.3.1 字符串形式1.…

网盘——私聊

在私聊这个功能实现中&#xff0c;具体步骤如下&#xff1a; 1、实现步骤&#xff1a; A、客户端A发送私聊信息请求&#xff08;发送的信息包括双方的用户名&#xff0c;聊天信息&#xff09; B、如果双方在线则直接转发给B&#xff0c;不在线则回复私聊失败&#xff0c;对方…

ProgressFlowmon的confluence接口存在任意命令执行漏洞(CVE-2024-2389)

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 ProgressFlowmon是一整套用于网络映射、应用程序性能…

客户端动态降级系统

本文字数&#xff1a;4576字 预计阅读时间&#xff1a;20分钟 01 背景 无论是iOS还是Android系统的设备&#xff0c;在线上运行时受硬件、网络环境、代码质量等多方面因素影响&#xff0c;可能会导致性能问题&#xff0c;这一类问题有些在开发阶段是发现不了的。如何在线上始终…

大气的免费wordpress模板

国产的wordpress模板&#xff0c;更适合中国人使用习惯&#xff0c;更符合中国老板的审美的大气wordpress企业官网建站模板。 WordPress模板&#xff0c;也称为主题&#xff0c;是用于定义WordPress网站或博客外观和功能的预设计文件集。这些模板使用HTML、CSS和PHP代码构建&a…

上传文件到HDFS

1.创建文件夹 hdfs -dfs -mkdir -p /opt/mydoc 2.查看创建的文件夹 hdfs -dfs -ls /opt 注意改文件夹是创建在hdfs中的&#xff0c;不是本地&#xff0c;查看本地/opt&#xff0c;并没有该文件夹。 3.上传文件 hdfs dfs -put -f file:///usr/local/testspark.txt hdfs://m…

【深度学习】Vision Transformer

一、Vision Transformer Vision Transformer (ViT)将Transformer应用在了CV领域。在学习它之前&#xff0c;需要了解ResNet、LayerNorm、Multi-Head Self-Attention。 ViT的结构图如下&#xff1a; 如图所示&#xff0c;ViT主要包括Embedding、Encoder、Head三大部分。Class …

Docker in Docker的原理与实战

Docker in Docker&#xff08;简称DinD&#xff09;是一种在Docker容器内部运行另一个Docker实例的技术。这种技术允许用户在一个隔离的Docker容器中创建、管理和运行其他Docker容器&#xff0c;从而提供了更灵活和可控的部署选项。以下是DinD的主要特点&#xff1a; 隔离性&am…

力扣打卡第一天

101. 对称二叉树 C&#xff1a; class Solution { public:bool isSymmetric(TreeNode* root) {return check(root->left,root->right);}bool check(TreeNode *p,TreeNode *q){ /**定义check方法用来检查两棵树是否是镜像的*/if (!p && !q) return true; /* 如…

基于SSM的物流快递管理系统(含源码+sql+视频导入教程+文档+PPT)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的物流快递管理系统2拥有三个角色&#xff1a; 管理员&#xff1a;用户管理、管理员管理、新闻公告管理、留言管理、取件预约管理、收件管理、货物分类管理、发件信息管理等 用户…

如何安全、高速、有效地利用IP代理爬取数据

陈老老老板&#x1f9d9;‍♂️ &#x1f46e;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f934;本文简述&#xff1a;如何安全、高速、有效地利用IP代理爬取数据 &#x1f473…

【数据结构-串-数组-广义表】

目录 1 串-理解1.1 串的抽象定义&#xff1a;-理解1.2 串的存储结构-不断掌握1.2.1 顺序存储结构&#xff1a;1.2.2 链式存储结构&#xff1a; 1.3 串的模式匹配算法&#xff1a;-掌握1.3.1 BF暴力求解算法-代码 -掌握1.3.2 KMP求解算法-代码--掌握 2 数组-不断掌握2.1 顺序存储…

WWW ‘24 | EarnMore: 如何利用强化学习来处理可定制股票池中的投资组合管理问题

WWW 24 | EarnMore: 如何利用强化学习来处理可定制股票池中的投资组合管理问题 原创 QuantML QuantML 2024-04-16 09:04 上海 Content 本文主要探讨了如何利用强化学习&#xff08;Reinforcement Learning, RL&#xff09;来处理可定制股票池&#xff08;Customizable Stock …

Golang | Leetcode Golang题解之第40题组合总和II

题目&#xff1a; 题解&#xff1a; func combinationSum2(candidates []int, target int) (ans [][]int) {sort.Ints(candidates)var freq [][2]intfor _, num : range candidates {if freq nil || num ! freq[len(freq)-1][0] {freq append(freq, [2]int{num, 1})} else {…