【多线程学习】深入探究定时器的重点和应用场景

简介: 【多线程学习】深入探究定时器的重点和应用场景

一.定时器

1.什么是定时器

在Java中定时器通常指的是一种能够按照预定的时间间隔执行任务的机制,简单来说,定时器就相当于一个"闹钟",指定一个(Runnable) 任务,以及指定一个时间,该任务因为定时器的缘故在线程中并不会立马就执行,而是到达某个指定的时间后,才执行

2.定时器的应用场景

Java中的定时器(Timer)在许多应用场景中都非常有用,下面是几个常见的例子:

  1. 定时任务执行
  • 定期清理缓存:应用程序可以设置定时器来定时清理不再需要的缓存数据,避免内存占用过高。
  • 数据备份:数据库系统可以使用定时器在每天的固定时间执行数据备份操作。
  • 日志滚动:系统可以按计划滚动日志文件,比如每24小时创建一个新的日志文件。
  1. 周期性更新
  • 动态刷新数据:网页应用程序中,定时器可用于刷新显示的内容,例如实时股票价格、天气预报等。
  • API轮询:在等待异步响应或资源的状态变化时,可以设置定时器定期发起API请求查询最新状态。
  1. 邮件通知
  • 提醒服务:例如,用户注册后发送确认邮件,或者每日/每周发送新闻简报、报告等。
  • 系统监控告警:当系统指标超过阈值时,定时器可以用来检查系统状态并在必要时发送警告邮件。
  1. 会话管理
  • 用户会话过期处理:在Web应用中,定时器可以用来检查并清理长时间未活动的用户会话。
  1. 心跳检测
  • 网络连接健康检查:客户端和服务端之间可以利用定时器发送心跳包来维持长连接,及时发现连接异常。
  1. 延迟执行
  • 延迟发送消息:例如在消息队列中,可以设置一条消息在一段时间后才投递给消费者。
  1. 调度任务
  • 批处理作业:在企业级应用中,定时器可以调度批处理作业在非高峰时段执行,减轻服务器负载。
  1. 社交媒体更新
  • 社交媒体平台可以使用定时器定期检查用户的动态更新,例如好友状态更改或新消息提醒。

总之,任何需要按照预设时间间隔或特定时间点执行任务的地方都可以考虑使用定时器机制。

3.如何在Java中创建定时器

在Java中创建定时器通常有两种方式,使用java.util.Timer类或ScheduledExecutorService接口。以下是两种方式的基本示例:

1.使用 Java.util.Timer 类

import java.util.Timer;
import java.util.TimerTask;
 
public class TimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
        // 任务类
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务执行了");
            }
        };
        
        // 安排任务在1秒后执行,然后每隔1秒执行一次
        timer.scheduleAtFixedRate(task, 1000, 1000);
        
        // 记得在程序结束时取消定时器,以避免潜在的内存泄漏
        // timer.cancel();
    }
}

2.使用ScheduledExecutorService接口

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
 
public class ScheduledExecutorExample {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("Scheduled任务执行了");
            }
        };
        
        // 安排任务在1秒后执行,然后每隔1秒执行一次
        executor.scheduleAtFixedRate(task, 1, 1, TimeUnit.SECONDS);
        
        // 记得在程序结束时关闭executor,以释放资源
        // executor.shutdown();
    }
}

3.两个方法的注意事项

  • 使用Timer时,所有的任务都由同一个线程顺序执行,如果某个任务执行时间过长,可能会影响其他任务的执行时间。
  • 使用ScheduledExecutorService时,可以控制任务执行的线程池,这使得它更适合执行可能阻塞的任务,或者需要并行执行的任务。
  • 在应用程序不再需要定时器时,应该调用timer.cancel()executor.shutdown()来停止定时器,避免线程泄露。
  • 如果希望立即停止所有正在执行的任务,并不再接受新的任务,可以使用executor.shutdownNow()方法
  • 简单来说,使用 Java.util.Timer 类 就是在后台创建一个 线程或多个线程 默认情况下,Timer只创建一个线程来顺序执行所有任务。这意味着如果一个任务的执行时间过长,它将阻塞队列中的其他任务。线程池允许更好地控制任务的执行,例如设置最大线程数、线程存活时间、工作队列等。使用线程池可以有效地管理资源,提高程序的响应速度和效率。

4.定时器的底层执行顺序

Java java.util.Timer 类作为基础定时器实现,其底层执行顺序遵循以下逻辑:

  1. 任务调度
  • 当用户创建 Timer 实例并使用 schedule() scheduleAtFixedRate() 方法来调度 TimerTask 时,这些任务会被添加到 Timer 的内部 TaskQueue 队列中。这个队列是按照任务的执行时间进行排序的,即按照每个任务下一次应该被执行的时间。
  1. 任务轮询
  • Timer 创建一个名为 TimerThread 的后台线程,这个线程会在启动后进入一个无限循环
  • 在循环中,TimerThread 不断地检查 TaskQueue 中是否有已到达执行时间的任务。
  • 如果发现一个或多个任务已经到期,则按照队列的顺序取出任务并执行它的 run() 方法。
  1. 执行顺序保证
  • 根据时间优先级排序,最先到期的任务会先被执行。
  • 对于固定延迟调度(schedule()),任务的实际执行时间可能受前一个任务执行时间和线程调度的影响,不一定严格按计划时间执行。
  • 对于固定速率调度(scheduleAtFixedRate()),即使前一个任务执行超时导致延迟,系统仍尝试按照固定的频率执行任务,即从上一次任务开始执行的时间点算起,每隔一定时间执行一次。
  1. 并发问题
  • 由于 Timer 是基于单线程模型的,如果一个任务执行时间过长或者抛出未捕获的异常,那么后续的任务可能会延迟执行,甚至无法执行。这是因为单个 Timer 的所有任务都在同一个 TimerThread 上执行,这意味着任务之间没有并发执行能力,且一旦遇到阻塞或异常情况,整个定时器的工作将会受到影响。

因此,虽然理论上 Timer 底层执行顺序是严格按照任务设定的时间顺序进行的,但在实际应用中,尤其是考虑到多任务环境下的线程调度不确定性以及单线程模型的局限性,应当谨慎使用 java.util.Timer 类以避免潜在的并发问题。对于更复杂或对定时任务执行顺序和可靠性要求较高的场景,推荐使用诸如 ScheduledExecutorService 这样的并发框架提供的服务。

5.使用java代码自定义一个定时器

通过了解定时器的底层执行顺序,为了帮助我们更好的理解定时器,我们就可以自己定义一个定时器.增加理解

// 定义一个实现了Comparable接口的定时任务类,用于存储Runnable任务及其执行时间
class MyTimerTask implements Comparable<MyTimerTask> {
    private Runnable runnable; // Runnable对象,代表具体要执行的任务
    private long time; // 记录该任务应该被执行的具体时间(系统当前时间+延迟时间)
 
    // 构造方法,传入一个Runnable任务和延迟执行的时间
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }
 
    // 获取任务的执行时间
    public long getTime() {
        return time;
    }
 
    // 执行任务
    public void run() {
        runnable.run();
    }
 
    // 重写compareTo方法,用于比较两个任务的执行时间,以便放入PriorityQueue按时间顺序排序
    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.getTime() - o.getTime());
    }
}
 
// 定时器类,负责调度任务
class MyTimer {
    private PriorityQueue<MyTimerTask> q = new PriorityQueue<>(); // 存储定时任务的优先级队列
    private Object locker = new Object(); // 同步锁对象,用于线程同步
 
    // 定时器构造方法,启动一个后台线程执行定时任务
    public MyTimer() {
        Thread t = new Thread(() -> {
            try {
                // 无限循环,直到程序结束
                while (true) {
                    synchronized (locker) {
                        // 如果队列为空,则等待新的任务加入
                        if (q.size() == 0) {
                            locker.wait();
                        }
 
                        // 获取队列中下一个即将执行的任务
                        MyTimerTask myTimerTask = q.peek();
                        long nowTime = System.currentTimeMillis();
 
                        // 检查当前时间是否大于等于任务执行时间
                        if (nowTime >= myTimerTask.getTime()) {
                            // 执行任务并从队列中移除已完成的任务
                            myTimerTask.run();
                            q.poll();
                        } else {
                            // 如果任务未到执行时间,线程等待剩余时间
                            locker.wait(myTimerTask.getTime() - nowTime);
                        }
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        t.start();
    }
 
    // 调度方法,将一个任务及其延迟执行时间添加到定时器中
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay); // 创建一个新的定时任务
            q.offer(task); // 将任务添加到优先级队列中
            locker.notify(); // 唤醒等待的任务调度线程
        }
    }
}
 
// 主函数,演示如何使用MyTimer类调度任务
public class Demo18 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
 
        // 调度三个任务,分别延迟1000ms、2000ms和3000ms执行
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        }, 1000);
 
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        }, 2000);
 
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        }, 3000);
    }
}

1.代码解析

  1. MyTimerTask 类是一个自定义的定时任务类,它封装了一个 Runnable 对象(即任务实体)和一个表示任务执行时间的 long 变量。由于实现了 Comparable 接口,任务可以根据其执行时间在优先级队列中进行排序。
  2. MyTimer 类是定时器类,维护了一个优先级队列 (PriorityQueue) 来存放 MyTimerTask 对象。在构造方法中启动了一个后台线程,该线程会不断地检查并执行优先级队列中最接近当前时间的任务。
  3. 在后台线程的无限循环中,首先检查优先级队列是否为空。如果为空,则调用 wait() 方法让当前线程进入阻塞状态,直到有新的任务被添加到队列中。然后取出队列顶端的任务,判断其执行时间是否已到。如果已到则执行任务并移除,否则继续等待剩余时间
  4. schedule 方法允许外部向定时器中添加一个 Runnable 任务和对应的延迟执行时间。将任务封装成 MyTimerTask 并添加至优先级队列中,随后调用 locker.notify() 唤醒等待的任务调度线程,使其重新检查队列并执行任务。

2.注意事项

1.该代码只是帮助更好的理解定时器的执行顺序以及一些执行原理,如果我们实际开发的时候需要用到定时器的话,我们最好的使用 java.util.Timer类或ScheduledExecutorService接口来定义定时器,

感谢你的阅读,祝你一天愉快



相关文章
|
23天前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
22天前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
29天前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
19 3
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
46 4
|
1月前
|
数据采集 存储 Java
Crawler4j在多线程网页抓取中的应用
Crawler4j在多线程网页抓取中的应用
|
1月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
31 3
|
1月前
|
Java 数据处理 数据库
Java多线程的理解和应用场景
Java多线程的理解和应用场景
49 1
|
1月前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
45 2
|
21天前
|
Java 开发者
Java中的多线程基础与应用
【10月更文挑战第24天】在Java的世界中,多线程是提高效率和实现并发处理的关键。本文将深入浅出地介绍如何在Java中创建和管理多线程,以及如何通过同步机制确保数据的安全性。我们将一起探索线程生命周期的奥秘,并通过实例学习如何优化多线程的性能。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编程的大门。
17 0
|
2月前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
60 3

热门文章

最新文章