JavaEE:多线程代码案例(定时器)

news2024/9/20 20:46:28

文章目录

  • 定时器
    • 介绍
    • Java标准库中的定时器
    • 定时器的实现

定时器

介绍

除了之前说过的单例模式,阻塞队列,线程池以外,定时器也是我们日常开发中常用的代码~

定时器相当于"闹钟".在现实生活中,当闹钟响时,我们就需要去完成一些事情.
同理,在代码中,也经常需要"闹钟机制".

比如在网络通信中,我们经常需要设定一个"超时时间".
在Java标准库中提供了定时器的实现.

Java标准库中的定时器

在Java中提供了Timer,Timer提供了一个方法,叫做schedule.
使用schedule就可以实现定时器的效果.
schedule后面有两个参数:

  1. 安排一件什么事,通过TimeerTask来表示.
  2. 时间(单位毫秒),在多久之后干活.

在使用schedule的时候,第二个参数指定的时间是delay值(多长时间后才执行),但是描述任务的时候,不太建议使用delay表示,最好使用"绝对时间"(时间戳)来表示.

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

public class Demo21 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // schedule后面有两个参数:
        //  1.安排一件什么事,通过TimeerTask来表示
        //  2.时间(单位毫秒),在多久之后干活
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },3000);
        System.out.println("程序开始运行");

    }
}

运行以上代码时,程序会首先打印"程序开始运行",等经过了3000ms后才会打印"hello".
在这里插入图片描述
Timer不是只能管理一个任务,而是可以管理多个任务:

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

public class Demo21 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // schedule后面有两个参数:
        //  1.安排一件什么事,通过TimeerTask来表示
        //  2.时间(单位毫秒),在多久之后干活
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
        System.out.println("程序开始运行");

    }
}

运行结果:
在这里插入图片描述

需要注意的是,在我们指定任务的时候,传入的参数是TimerTask而不是Runnable,实际上TimerTask就是基于Runnable又封装了一层.
在这里插入图片描述

定时器的实现

接下来让我们自己实现一个简单的定时器~
要想实现定时器需要做到以下3点:

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

代码:

import java.util.*;

class MyTimerTask implements Comparable<MyTimerTask> {
    private Runnable runnable;
    //此处这里的time,通过毫秒时间戳,来表示这个任务具体啥时候执行
    private long time;

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //把当前时间加上delay,得到了一个绝对的时间戳,来告诉系统啥时候执行.
        this.time = System.currentTimeMillis() + delay;
    }

    //真正要执行的逻辑
    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

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

class MyTimer {
//    // 使用List保存,并不是一个好的选择,我们在后续执行任务列表中的任务的时候,就需要依次遍历每个元素
//    // 等程序执行完毕后,还需要把对应的任务从List中删除掉
//    // 可以看到效率并不是很高.
//    private List<MyTimerTask> list = new ArrayList<>();
    // 我们可以使用堆!!
    // 因为堆可以很快的找到"最小值"/"第二小"/"第三小"
    // 而我们是按照时间来执行任务的,我们只需要确定所有的任务中,时间最小的任务,判断它是否到时间该执行了即可.
    // 时间最小的任务,如果还没到时间,那么其他任务就更不可能到时间了~

    // 别忘了重写compareTo方法
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();

    private Object locker = new Object();

    public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                while (true) {
                    synchronized (locker) {
                        // 确保队列不为空
                        while (queue.isEmpty()) {
                            //为空,阻塞
                            locker.wait();
                        }
                        MyTimerTask current = queue.peek();
                        // 判断当前时间是否大于等于任务时间
                        if (System.currentTimeMillis() >= current.getTime()) {
                            // 要执行任务
                            current.run();
                            // 把执行过的任务,从队列中删除
                            queue.poll();
                        } else {
                            // 不执行,使用wait的时候会释放cpu资源,让别人使用
                            locker.wait(current.getTime()-System.currentTimeMillis());

                            // 这个地方不能用sleep,因为可能会出现以下情况
                            // 1.你在sleep 1h15min 的过程中,新来了个更早的任务,比如 11:30 执行
                            //  但是该线程仍在sleep.
                            //  如果使用wait,每次新来任务,都会把wait唤醒,从而重新设定等待的时间
                            // 2. sleep休眠的时候,不会释放锁(抱着锁睡)
                            //  这意味着其他人就拿不到锁了,这就导致新的任务就添加不进来
                            // 因此使用wait更好!

                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        t.start();
    }

    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            queue.offer(myTimerTask);
            // 唤醒
            locker.notify();
        }
    }
}


public class Demo22 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() -> {
            System.out.println("hello1");
        }, 1000);
        myTimer.schedule(() -> {
            System.out.println("hello2");
        }, 2000);
        myTimer.schedule(() -> {
            System.out.println("hello3");
        }, 3000);
    }
}

有人可能会问了,为啥不使用PriorityBlockingQueue而是自己加锁?
别急,听我慢慢解释:

MyTimerTask current = queue.peek();

首先看这里,这里的take可能会触发阻塞.(PriorityBlockingQueue中的锁)

locker.wait(current.getTime()-System.currentTimeMillis());

再看这里,这里也可能会触发阻塞.(locker锁)

想一想如果上述的阻塞都触发了,那么代码中的锁就变成两把了.
两把锁就有可能会出现死锁的情况.
想要避免死锁,这就需要我们精心控制这里的加锁顺序.
但是这样的话,代码的复杂程度就又提高了不少.
但是其实这里不使用阻塞队列,通篇代码一把锁就可以解决所有问题.
那为啥还要使用PriorityBlockingQueue呢?.

业界实现定时器,除了优先级队列的方式之外,还有一种经典的实现方式,“时间轮”(也是一个巧妙设计的数据结构)

定时器这个东西,很重要,特别常用,尤其是在后端开发中.与"阻塞队列类似",它也会有专门的服务器,用来在分布式系统中实现定时器这样的效果.

本文到这里就结束了~

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

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

相关文章

力扣406-根据身高重建队列(java详细题解)

题目链接&#xff1a;406. 根据身高重建队列 - 力扣&#xff08;LeetCode&#xff09; 前情提要&#xff1a; 因为本人最近都来刷贪心类的题目所以该题就默认用贪心方法来做。 贪心方法&#xff1a;局部最优推出全局最优。 如果一个题你觉得可以用局部最优推出全局最优&…

【自用16.】C++类

类的构成 类的设计 代码demo #include <iostream> #include <Windows.h> #include <string>using namespace std;// 定义一个“人类” class Human { public: //公有的&#xff0c;对外的void eat(); //方法&#xff0c; “成员函数”void sleep();void …

从零开始搭建本地安全 AI 大模型攻防知识库

本文将系统分享从零开始搭建本地大模型问答知识库过程中所遇到的问题及其解决方案。 1 概述 目前&#xff0c;搭建大语言问答知识库能采用的方法主要包括微调模型、再次训练模型以及增强检索生成&#xff08;RAG&#xff0c;Retrieval Augmented Generation&#xff09;三种方…

51单片机-定时器介绍

时间&#xff1a;2024.8.31 作者&#xff1a;Whappy 目的&#xff1a;手撕51 代码&#xff1a; 现象&#xff1a;

【fastapi】fastapi的hello world

新建这样的目录结构 main.py的代码如下 from fastapi import FastAPI from fastapi.templating import Jinja2Templatesapp FastAPI()# 初始化 Jinja2 模板引擎 templates Jinja2Templates(directory"templates")app.get("/") async def home():contex…

AI编码新时代:免费人工智能助手Blackbox AI

前言&#xff1a; 在当今快速发展的科技时代&#xff0c;人工智能已经渗透到我们生活的方方面面&#xff0c;从智能手机的语音助手到智能家居控制系统&#xff0c;再到在线客服和个性化推荐算法&#xff0c;AI智能工具正变得越来越普遍。它们以其高效、智能和用户友好的特性&am…

已成功入职小米大模型岗!!大模型面试其实挺水的,hr听到这些直接过

小米大模型面试180题 1、目前比较受欢迎的开源大模型有哪些&#xff1f; GPT系列&#xff1a;由OpenAl开发的生成式预训练模型&#xff0c;如 GPT-3。 BERT系列&#xff1a;由Google开发的转换式预训练模型&#xff0c;如BERT、RoBERTa等。 T5系列&#xff1a;由Google开发的基…

Docker 实战加速器(紧急情况!镜像库全面失效,一招解决Docker无法下载)

现象: Docker 加速器原理 Docker 镜像加速器可以帮助你更快地从 Docker Hub 或其他镜像仓库下载镜像,特别是在网络环境较差或访问 Docker Hub 较慢的情况下。常见的加速器提供商包括阿里云、网易云等。 Docker 加速器原理 Docker 镜像加速器通过在本地设置一个代理服务器,…

【ubuntu笔记】Ubuntu下SourceInsight 4.x中文乱码问题

Options->Preferences->Files&#xff0c;最底部有个Default Encoding选项&#xff0c;选择UTF-8 Options->Preferences->Syntax Decorations->File Types->Screen Font&#xff0c;选择一个可以显示中文的字体&#xff0c;例如"文泉驿等宽微米黑"…

wsl下将Ubuntu从c盘移动到其他盘

一、概述 因为自己的C盘内存不足&#xff0c;加上之后需要在Ubuntu下面下载许多的内容和东西&#xff0c;需要将其移动到d盘上面&#xff0c;这样可以拥有更大的空间。这里记载了一下自己的操作过程。 二、具体步骤 &#xff08;一&#xff09;过程 1.查看当前系统中wsl分发版…

在uni-app中使用SQLite

目录 1、引入sqlite模块 2、sqlite文件结构 3、初始化文件index.js 4、打开数据库 5、查询数据 6、可视化测试 SQLite是一个进程内的库&#xff0c;实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库&#xff0c;这意味着与其他…

【网络安全】服务基础第一阶段——第八节:Windows系统管理基础---- Web服务与虚拟主机

目录 一、WWW概述 1.1 HTML 1.2 URI与URL 1.2.1 URL&#xff08;统一资源标识符&#xff0c;Uniform Resource Locator&#xff09; 1.3 HTTP 1.3.1 HTTP请求&#xff1a; 1.3.2 HTTP响应 1.3.3 状态码 1.4常见Web URL格式 实验一、网站搭建 1&#xff09;访问失败可…

实训day29(8.15)

一、python管理mysql 1、搭建主mysql [rootmysql57 ~]# tar -xf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz [rootmysql57 ~]# cp -r mysql-5.7.44-linux-glibc2.12-x86_64 /usr/local/mysql [rootmysql57 ~]# rm -rf /etc/my.cnf [rootmysql57 ~]# mkdir /usr/local/mysql…

DCB简介

DCB协议组主要用于构建无丢包以太网&#xff0c;以满足数据中心网络融合后的QoS需求。 数据中心网络融合后&#xff0c;LAN、SAN和IPC流量的QoS需求上存在较大的差异&#xff1a; SAN流量对丢包很敏感且要求报文在传输过程中是保序的。LAN流量允许丢包&#xff0c;只需要设备…

MySQL基础学习:MySQL主从复制如何实现

这里写自定义目录标题 一、为什么使用MySQL主从二、主从复制原理是什么三、如何保证主从一致 一、为什么使用MySQL主从 保证服务的高可用&#xff1a;当主机宕机的时候可以选择一个从节点作为主节点&#xff0c;保证了我们服务的高可用。保证容灾备份&#xff1a;主库上的磁盘…

每日AIGC最新进展(54):中科大提出Pose引导的图像生成模型、韩国科技学院提出发型控制模型、北大提出风格生成数据集CSGO

Diffusion Models专栏文章汇总&#xff1a;入门与实战 GRPose: Learning Graph Relations for Human Image Generation with Pose Priors 在过去的研究中&#xff0c;基于扩散模型的人工生成技术在根据特定条件合成高质量人像方面取得了显著进展。然而&#xff0c;尽管之前的方…

【算法】前缀和例题讲解

例一&#xff1a; 724. 寻找数组的中心下标 思路&#xff1a; 典型的前缀和题目&#xff0c;我们只需要创建前缀和数组和后缀和数组&#xff0c;然后一一寻找两者相等的下标即可。 代码&#xff1a; class Solution { public:int pivotIndex(vector<int>& nums) …

华发股份:销售排名稳居TOP10 谱写高质量发展新篇章

2024年8月30日晚&#xff0c;华发股份&#xff08;600325.SH&#xff09;发布2024年半年度报告。报告显示&#xff0c;公司实现营业总收入248.42亿元&#xff0c;归母净利润12.65亿元。面对复杂多变的宏观环境和行业调整的挑战&#xff0c;华发股份依然能够稳固其经营根基&…

Elastic Search(五):索引生命周期管理 - ilm

目录 1 ES&#xff1a;索引生命周期管理 - ilm1.1 介绍1、ILM阶段转换阶段执行阶段操作 1.2 索引生命周期操作1、设置 索引生命周期 1.3 索引生命周期管理1、创建 生命周期策略2、创建索引模板&#xff0c;模板中关联 policy3、创建符合模板的起始索引&#xff0c;设置别名(即我…

无人机操控师技术及前景详解

随着科技的飞速发展和无人机技术的日益成熟&#xff0c;无人机在各行各业的应用越来越广泛&#xff0c;从农业植保、物流配送到影视拍摄、灾害救援&#xff0c;无人机技术正深刻改变着传统行业的运作模式。在这一背景下&#xff0c;无人机操控师作为无人机技术的核心操作者&…