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

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

一.定时器

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天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
88 6
|
21天前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
28 2
|
26天前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
26天前
|
存储 监控 安全
深入理解ThreadLocal:线程局部变量的机制与应用
在Java的多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将深入探讨`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
50 2
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
52 6
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
1月前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
2月前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
30 3
|
2月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
64 4