Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >JDK中的timer正确的打开与关闭

JDK中的timer正确的打开与关闭

作者头像
一觉睡到小时候
发布于 2020-05-27 15:12:51
发布于 2020-05-27 15:12:51
1.8K00
代码可运行
举报
文章被收录于专栏:国产程序员国产程序员
运行总次数:0
代码可运行

Timer和TimerTask

Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。

TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。

Timer的调度

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {

    public static void main(String args[]){
        new Reminder(3);
    }

    public static class Reminder{
        Timer timer;

        public Reminder(int sec){
            timer = new Timer();
            timer.schedule(new TimerTask(){
                public void run(){
                    System.out.println("Time's up!");
                    timer.cancel();
                    System.out.println("Time's shutdown!");
                }
            }, sec*1000);
        }
    } 
}

控制台输出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Time's up!
Time's shutdown!

从这个例子可以看出一个典型的利用timer执行计划任务的过程如下:

  • new一个TimerTask的子类,重写run方法来指定具体的任务,在这个例子里,我用匿名内部类的方式来实现了一个TimerTask的子类
  • new一个Timer类,Timer的构造函数里会起一个单独的线程来执行计划任务。

jdk的实现代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Timer() {
        this("Timer-" + serialNumber());
    }

    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

Timer的关闭

在JDK1.5以后,文档中有这么一句话: 对 Timer 对象最后的引用完成后,并且 所有未处理的任务都已执行完成后,计时器的任务执行线程会正常终止(并且成为垃圾回收的对象)。但是这可能要很长时间后才发生。

System.gc()

系统默认当Timer运行结束后,如果没有手动终止,那么则只有当系统的垃圾收集被调用的时候才会对其进行回收终止。 因此,可以手动System.gc(); 但是Sytem.gc()在一个项目中是不能随便调用的。因为一个tomcat只启动一个进程,而JVM的垃圾处理器也只有一个,所以在一个工程里运行System.gc也会影响到其他工程。

cancle()

首先看cancle方法的源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

没有显式的线程stop方法,而是调用了queue的clear方法和queue的notify方法,clear是个自定义方法,notify是Objec自带的方法,很明显是去唤醒wait方法的。

clear方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /**
  * Removes all elements from the priority queue.
  */
void clear() {
        // Null out task references to prevent memory leak
        for (int i=1; i<=size; i++)
            queue[i] = null;

        size = 0;
    }

clear方法很简单,就是去清空queue,queue是一个TimerTask的数组,然后把queue的size重置成0,变成empty.还是没有看到显式的停止线程方法,回到最开始new Timer的时候,看看new Timer代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Timer() {
        this("Timer-" + serialNumber());
    }

   /**
     * Creates a new timer whose associated thread has the specified name.
     * The associated thread does <i>not</i>
     * {@linkplain Thread#setDaemon run as a daemon}.
     *
     * @param name the name of the associated thread
     * @throws NullPointerException if {@code name} is null
     * @since 1.5
     */
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

看看这个内部变量thread:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /**
  * The timer thread.
 */
 private TimerThread thread = new TimerThread(queue);

不是原生的Thread,是自定义的类TimerThread.这个类实现了Thread类,重写了run方法,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }

最后是这个mainLoop方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   /**
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
}

可以看到wait方法,之前的notify就是通知到这个wait,然后clear方法在notify之前做了清空数组的操作,所以会break,线程执行结束,退出。

Listener中的Timer

很多业务中需要Timer一直执行,不会执行一次后就关闭,上面的例子中,timer调用cancel方法后,该timer就被关闭了。

监听器的实现方式有多种,这里我们说一下实现ServletContextListener接口。 该接口中有2个方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface ServletContextListener extends EventListener {
    void contextInitialized(ServletContextEvent var1);

    void contextDestroyed(ServletContextEvent var1);
}

即上下文的初始化和销毁。

我们来看一个实例 Listener

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyListener implements ServletContextListener {

    private Log log = LogFactory.getLog(MyListener.class);

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        Timer timer = new Timer();
        timer.schedule(new MyTask(),5000,5000);
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}

Task

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyTask extends TimerTask {

    @Override
    public void run() {
        System.out.println("timer 正在执行");
    }
}

这样当程序启动的时候,在监听器的初始化中,timer会梅5秒执行一次

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timer 正在执行
timer 正在执行
timer 正在执行
timer 正在执行

此次程序中我们没有去调用timer的cancel方法,这样会存在一个问题,就是产生的timer一直不会被关闭,就像上面说的只有当系统的垃圾收集被调用的时候才会对其进行回收终止。

同时tomcat日志会打印错误

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
28-Apr-2020 14:23:24.892 警告 [http-nio-8080-exec-23] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web应用程序[nyzft]似乎启动了一个名为[Timer-3]的线程,但未能停止它。这很可能会造成内存泄漏。线程的堆栈跟踪:[
 java.lang.Object.wait(Native Method)
 java.lang.Object.wait(Object.java:502)
 java.util.TimerThread.mainLoop(Timer.java:526)
 java.util.TimerThread.run(Timer.java:505)]

问题的原因就是我们没有手动去关闭timer,但是如果去调用cancel方法,真实的场景timer只会被执行一次,不符合业务要求。 因此可以通过listener的contextDestroyed去关闭timer

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MyListener implements ServletContextListener {

    private Log log = LogFactory.getLog(MyListener.class);

    private Timer timer;

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        timer = new Timer();
        timer.schedule(new MyTask(),5000,5000);
        System.out.println("执行");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        timer.cancel();
        System.out.println("关闭");
    }
}

启动程序,过几秒钟后再关闭程序,查看控制台输出

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
执行
timer 正在执行
timer 正在执行
[2020-04-29 09:44:19,609] Artifact ssm-nyzft:war exploded: Artifact is deployed successfully
[2020-04-29 09:44:19,609] Artifact ssm-nyzft:war exploded: Deploy took 38,550 milliseconds
timer 正在执行
timer 正在执行
timer 正在执行
timer 正在执行
E:\Kit\Tomcat\tomcat8\apache-tomcat-8.5.39\bin\catalina.bat stop
Disconnected from the target VM, address: '127.0.0.1:52706', transport: 'socket'
Using CATALINA_BASE:   "C:\Users\Administrator\.IntelliJIdea2019.1\system\tomcat\Unnamed_ssm-nyzft_2"
Using CATALINA_HOME:   "E:\Kit\Tomcat\tomcat8\apache-tomcat-8.5.39"
Using CATALINA_TMPDIR: "E:\Kit\Tomcat\tomcat8\apache-tomcat-8.5.39\temp"
Using JRE_HOME:        "E:\Kit\JDK\JDK"
Using CLASSPATH:       "E:\Kit\Tomcat\tomcat8\apache-tomcat-8.5.39\bin\bootstrap.jar;E:\Kit\Tomcat\tomcat8\apache-tomcat-8.5.39\bin\tomcat-juli.jar"
29-Apr-2020 09:44:40.511 淇℃伅 [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
29-Apr-2020 09:44:40.512 淇℃伅 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8081"]
29-Apr-2020 09:44:40.638 淇℃伅 [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"]
29-Apr-2020 09:44:40.750 淇℃伅 [main] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina]
关闭
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 国产程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JDK Timer 实现原理分析
注意其中最大的区别,在于 schedule 调用 sched 函数时,将传入的 period 取反了。如果某次执行任务的开始时间延后了,那么此后的每次任务都会延迟。
Yano_nankai
2018/10/08
4830
JDK Timer 实现原理分析
Java Timer定时器原理
做项目很多时候会用到定时任务,比如在深夜,流量较小的时候,做一些统计工作。早上定时发送邮件,更新数据库等。这里可以用Java的Timer或线程池实现。Timer可以实现,不过Timer存在一些问题。他起一个单线程,如果有异常产生,线程将退出,整个定时任务就失败。
技术从心
2019/08/06
1.4K0
TimerTask(addin timer语音)
其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,例如这样:
全栈程序员站长
2022/08/02
6270
【Android 异步操作】Timer 定时器 ( Timer 与 TimerTask 基本使用 | Timer 定时器常用用法 | Timer 源码分析 )
2 . 定时器任务执行规则 : Timer 执行任务是 串行执行 的 , 同一时间只能执行一个任务 ;
韩曙亮
2023/03/28
3.6K0
看 JDK 源码,解几个疑问
解包 jdk_sec-1_5_0-src-jrl,在/j2se/src/share/classes/java/util 中找到 Timer 类。
四火
2022/07/15
1600
看 JDK 源码,解几个疑问
java定时器之Timer使用与原理分析[通俗易懂]
Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。
全栈程序员站长
2022/09/08
7660
线上服务启动卡死,堆栈分析
这一步,就是等待队列为非空的时候,才会执行下去,但是现在队列一直为空,线程都在等待。
MickyInvQ
2020/09/27
2.4K0
线上服务启动卡死,堆栈分析
面试必问!JDK 中定时器是如何实现的?
Demo中使用了Timer实现了一个定时任务,该任务在每天12点开始执行,并且每隔2秒执行一次。
Java小咖秀
2021/04/20
4130
定时器的实现原理及参考
如果让你来实现一个定时器的功能,简单点就是,每隔n秒,去执行一次A任务,你打算怎么实现
烂猪皮
2022/05/17
4660
深入 Java Timer 定时任务调度器实现原理
使用 Java 来调度定时任务时,我们经常会使用 Timer 类搞定。Timer 简单易用,其源码阅读起来也非常清晰,本节我们来仔细分析一下 Timer 类,来看看 JDK 源码的编写者是如何实现一个稳定可靠的简单调度器。
老钱
2018/12/27
1.2K0
定时任务的实现原理,看完就能手撸一个!
在很多业务的系统中,我们常常需要定时的执行一些任务,例如定时发短信、定时变更数据、定时发起促销活动等等。
macrozheng
2021/01/06
4.5K0
定时任务的实现原理,看完就能手撸一个!
Java基础知识扫盲
Arrays.sort是Java中提供的对数组进行排序的方法,根据参数类型不同,它提供了很多重载方法:
ma布
2024/10/21
580
Java基础知识扫盲
Java 定时任务实现原理详解[通俗易懂]
在jdk自带的库中,有两种技术可以实现定时任务。一种是使用Timer,另外一个则是ScheduledThreadPoolExecutor。下面为大家分析一下这两个技术的底层实现原理以及各自的优缺点。
全栈程序员站长
2022/07/25
9070
实现线程的方式到底有几种?
这篇文章主要讲解实现线程的方式到底有几种?以及实现 Runnable 接口究竟比继承 Thread 类实现线程好在哪里?
武培轩
2020/02/16
1.9K0
定时器算法
在日常开发中, 定时任务是一个比较关键的功能。 Java 中一般使用 JDK 中 Timer、ScheduledExecutorService 和调度框架 Quartz等。 通常用于实现延时任务, 周期性任务等, 一般会有两种需求:
leobhao
2022/06/28
9740
定时器算法
手把手教你写一个延时队列
这种实现方式下多个定时任务需要开启多个线程,而且线程在做无意义sleep,消耗资源,性能低下。
派大星在吗
2021/12/18
5040
Java Review - 使用Timer时需要注意的事情
先说结论 当一个Timer运行多个TimerTask时,只要其中一个TimerTask在执行中向run方法外抛出了异常,则其他任务也会自动终止。
小小工匠
2021/11/22
3650
Java Review - 使用Timer时需要注意的事情
从Timer中学习优先队列的实现
Timer是Java定时器的实现,用来调度定时执行的任务和执行一次的任务,就像JavaScript的setInterval和setTimeout的意思,它也可以作为后台程序(Daemon)运行。
用户3579639
2018/10/22
7200
从Timer中学习优先队列的实现
timer定时器用法_定时器怎么调时间
  在JDK类库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一个任务。此类也常用来做一下周期性同步工作,代替它的有quartz、SpringTask。Timer类的主要作用是设置计划任务,但封装任务的类是TimerTask类(实际该类是一个抽象类,执行任务的代码要放在该类的子类中)。Timer类的主要方法列表如下:
全栈程序员站长
2022/09/20
2.5K0
timer定时器用法_定时器怎么调时间
Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代「建议收藏」
继续并发,上篇博客对于ScheduledThreadPoolExecutor没有进行介绍,说过会和Timer一直单独写一篇Blog.
全栈程序员站长
2022/09/05
5000
推荐阅读
相关推荐
JDK Timer 实现原理分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档