【JAVA定时器】四种常见定时器的原理和简单实现,有图易懂 中

简介: 【JAVA定时器】四种常见定时器的原理和简单实现,有图易懂 中

3.Timer().schedule创建任务

3.1.样例

使用非常简单,这里先给出样例,在对照进行介绍

代码如下

package com.yezi_tool.demo_basic.test;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
@Component
public class TimerTest {
    private Integer count = 0;
    public TimerTest() {
        testTimer();
    }
    public void testTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    //do Something
                    System.out.println(new Date().toString() + ": " + count);
                    count++;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 0, 1000);
    }
}

执行结果

可以看到每隔1s打印一次count并自增1

3.2.介绍

核心包括Timer和TimerTask,均为jkd自带的工具类,代码量分别为721行和162行(包括注释),都不多,有兴趣的可以直接看看源码

3.2.1.TimerTask

TimerTask实际上就是一个Runnable而已,继承Runnable并添加了几个自定义的参数和方法,没啥好介绍的,有兴趣可以看源码

3.2.2.Timer

Timer字面意思即定时器,为jkd自带的工具类,提供定时执行任务的相关功能

实际上包括三个类:

Timer:即定时器主类,负责管理所有的定时任务,每个Timer拥有一个私有的TaskQueue和TimerThread,

TaskQueue:即任务队列,Timer生产任务,然后推到TaskQueue里存放,等待处理,被处理掉的任务即被移除掉

TaskQueue实质上只有一个长度为128的数组用于存储TimerTask、一个int型变量size表示队列长度、以及对这两个数据的增删改查

TimerThread:即定时器线程,线程会共享TaskQueue里面的数据,TimerThread会对TaskQueue里的任务进行消耗

TimerThread实际上就是一个Thread线程,会不停的监听TaskQueue,如果队列里面有任务,那么就执行第一个,并将其删除(先删除再执行)

流程分析

Timer生产任务(实际上是从外部接收到任务),并将任务推到TaskQueue里面存放,并唤醒TaskQueue线程(queue.notify())

TimerThread监听TaskQueue,若里面有任务则将其执行并移除队里,若没有任务则让队列等待(queue.wait())

这么一看,这不就是典型的==生产者/消费者模式==,timer负责生产(实际上是接受),而TimerThread负责消费,TaskQueue作为中转仓库

构造方法

构造的时候会设置定时器线程的名字并将其启动

完整格式如下,其中两个参数均可缺省

public Timer(String name, boolean isDaemon)

1

name:即线程名,用于区分不同的线程,缺省的时候默认使用"Timer-" + serialNumber()生成唯一线程名

isDaemon:是否是守护线程,缺省的时候默认为否,有啥区别请自行了解,有机会的话我也会整理笔记

核心方法

核心方法有添加任务、取消任务和净化三种

添加任务有6中公用方法(实际最后使用同一种私有方法)

schedule(TimerTask task, long delay):指定任务task,在delay毫秒延迟后执行

schedule(TimerTask task, Date time):指定任务task,在time时间点执行一次

schedule(TimerTask task, long delay, long period):指定任务task,延迟delay毫秒后执行第一次,并在之后每隔period毫秒执行一次

schedule(TimerTask task, Date firstTime, long period):指定任务task,在firstTime的时候执行第一次,之后每隔period毫秒执行一次

scheduleAtFixedRate(TimerTask task, long delay, long period):作用与schedule一致

scheduleAtFixedRate(TimerTask task, Date firstTime, long period):作用与schedule一致

实际上最后都会使用sched(TimerTask task, long time, long period),即指定任务task,在time执行第一次,之后每隔period毫秒执行一次

schedule使用系统时间计算下一次,即System.currentTimeMillis()+period

而scheduleAtFixedRate使用本次预计时间计算下一次,即time + period

对于耗时任务,两者区别较大,请按需求选择,瞬时任务无区别

取消任务方法:cancel(),会将任务队列清空,并堵塞线程,且不再能够接受任务(接受时报错),并不会销毁本身的实例和其内部的线程

净化方法:purge(),净化会将队列里所有被取消的任务移除,对剩余任务进行堆排序,并返回移除任务的数量

补充

如何保证第一个任务是执行时间最早的

任务队列会在每一次添加任务和删除任务时,进行堆排序矫正,净化也会对剩余任务重新堆排序

cancel的时候线程如何处理

定时器线程进行堵塞处理,并没有销毁,在执行当前任务后就不会执行下一次了,但是**线程并没有销毁**

所以尽量不要创建太多timer对象,会增加服务器负担

3.3.使用步骤

1.初始化Timer

Timer timer=new Timer();

2.初始化task

private class MyTask extends TimerTask {
        @Override
        public void run() {
            try {
                //do Something
                System.out.println(new Date().toString() + ": " + count);
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
MyTask myTask=new MyTask();

3.添加任务

timer.schedule(myTask, 5000, 3000);

完整代码:

package com.yezi_tool.demo_basic.test;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
@Component
public class TimerTest {
    private Integer count = 0;
    public TimerTest() {
        testTimer2();
    }
    public void testTimer2() {
        Timer timer = new Timer();
        MyTask myTask = new MyTask();
        timer.schedule(myTask, 0, 1000);
    }
    private class MyTask extends TimerTask {
        @Override
        public void run() {
            try {
                //do Something
                System.out.println(new Date().toString() + ": " + count);
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

当然可以缩写为样例里面的写法,更加简洁,请按照自己需求修改

4.线程

线程应该是最常见的实现方案,创建一个线程执行任务即可,举例几个不同的写法,代码如下

4.1.使用thread + runnable

package com.yezi_tool.demo_basic.test;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class ThreadTest {
    private Integer count = 0;
    public ThreadTest() {
        test1();
    }
    public void test1() {
        new Thread(() -> {
            while (count < 10) {
                System.out.println(new Date().toString() + ": " + count);
                count++;
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

4.2.使用线程池 + runnable

package com.yezi_tool.demo_basic.test;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Component
public class ThreadTest {
    private static final ExecutorService threadPool = Executors.newFixedThreadPool(5);// 线程池
    private Integer count = 0;
    public ThreadTest() {
        test2();
    }
    public void test2() {
        threadPool.execute(() -> {
            while (count < 10) {
                System.out.println(new Date().toString() + ": " + count);
                count++;
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

4.3.使用ScheduledTask + runnable

ScheduledTask 有11种添加任务的方法,详情直接查看文件TaskScheduler.java,这里给出常用的几个示例

设置触发频率为3000毫秒

package com.yezi_tool.demo_basic.test;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class ThreadTest {
    private Integer count = 0;
    private final TaskScheduler taskScheduler;
    public ThreadTest(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
        test3();
    }
    public void test3() {
        taskScheduler.scheduleAtFixedRate(() -> {
            System.out.println(new Date().toString() + ": " + count);
            count++;
        }, 3000);
    }
}

设置触发时间为每天凌晨1点

package com.yezi_tool.demo_basic.test;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class ThreadTest {
    private Integer count = 0;
    private final TaskScheduler taskScheduler;
    public ThreadTest(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
        test4();
    }
    public void test4() {
        taskScheduler.schedule(() -> {
            System.out.println(new Date().toString() + ": " + count);
            count++;
        }, new CronTrigger("0 0 1 * * ?"));
    }
}
目录
相关文章
|
7月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
215 0
|
7月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
290 0
|
7月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
258 24
|
8月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
273 2
|
8月前
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
710 13
|
8月前
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
|
8月前
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
6109 1
|
8月前
|
存储 算法 安全
Java中的对称加密算法的原理与实现
本文详细解析了Java中三种常用对称加密算法(AES、DES、3DES)的实现原理及应用。对称加密使用相同密钥进行加解密,适合数据安全传输与存储。AES作为现代标准,支持128/192/256位密钥,安全性高;DES采用56位密钥,现已不够安全;3DES通过三重加密增强安全性,但性能较低。文章提供了各算法的具体Java代码示例,便于快速上手实现加密解密操作,帮助用户根据需求选择合适的加密方案保护数据安全。
538 58