当前位置: 首页 > news >正文

Java EE初阶——定时器和线程池

 3. 定时器

定时器(Timer) 是一种用于在指定时间间隔后执行任务或周期性执行任务的工具。它广泛应用于定时任务、超时控制、周期性操作等场景。

Java 提供了多种定时器实现方式,适用于不同场景:

1. Timer 和 TimerTask(传统方式)

  • 核心类
    • Timer:定时器核心类,Timer 核⼼⽅法为 schedule ,负责调度任务。
    • TimerTask:抽象类,代表可调度的任务,需重写 run() 方法。
import java.util.Timer;
import java.util.TimerTask;public class Test {public static void main(String[] args) throws InterruptedException {//创建 Timer 实例Timer timer = new Timer();// 延迟 2 秒执行任务1timer.schedule(new TimerTask(){//创建了一个TimerTask实例//重新 run 方法,@Overridepublic void run(){System.out.println("任务");}},2000);// Thread.sleep(2000);// timer.cancel();//终止定时器}
}

         

schedule(TimerTask task, long delay)

  • new Timer.schedule()方法用于安排任务的执行。

  • 第一个参数是一个TimerTask对象,通过匿名内部类的方式创建了一个TimerTask实例,表示任务,并重写了run()方法。

    • run()方法中,打印了当前时间(以毫秒为单位,从1970年1月1日00:00:00 GMT开始计算)。

  • 第二个参数是延迟时间,单位为毫秒。定时器内部有一个线程,到延迟时间后就会执行任务。

执行流程:

  1. 程序启动,创建Timer实例

  2. 调用schedule方法安排任务

  3. Timer内部的后台线程开始计时

  4. 2秒后,Timer的后台线程调用TimerTaskrun方法

注意事项:

  1. Timer创建的线程默认是非守护线程(用户线程),即使main方法结束,程序也不会退出,除非调用timer.cancel()或所有非守护线程都结束。

  2. 如果需要在任务执行后让程序退出,可以:

    • 调用timer.cancel()取消定时器

    • 或者将定时器设置为守护线程:Timer timer = new Timer(true);

  3. 如果run方法中抛出未捕获的异常,定时器的执行线程会终止,但不会影响其他线程。

  • 特点
    • 单线程调度:所有任务由同一个线程按顺序执行,任务执行延迟会影响后续任务。
    • 简单易用:适合轻量级定时任务。

1. Timer的schedule方法

方法说明
schedule(TimerTask task, long delay)延迟指定时间后执行任务
schedule(TimerTask task, Date time)在指定时间执行任务
schedule(TimerTask task, long delay, long period)延迟指定时间后开始,以固定间隔重复执行
schedule(TimerTask task, Date firstTime, long period)从指定时间开始,以固定间隔重复执行

2. cancel()方法

cancel() 方法用于 终止定时器并取消所有已安排但尚未执行的任务

  • 终止 Timer 的后台线程:调用后,Timer 的调度线程会被终止,不再执行任何任务。

  • 取消所有待执行的任务:所有已通过 schedule() 或 scheduleAtFixedRate() 安排但尚未执行的任务都会被取消。

  • 不会影响正在执行的任务:如果某个任务正在执行,cancel() 不会中断它,但后续任务不会再被执行。

注意事项:

  1. cancel() 只能调用一次

    • 如果多次调用 cancel(),后续调用不会有任何效果。

    • 调用后,Timer 对象不能再安排新任务(会抛出 IllegalStateException)。

  2. Timer 线程不会立即终止

    • cancel() 只是标记 Timer 为已取消,正在执行的任务(如果有)会继续完成。

    • 如果希望立即停止正在运行的任务,可以使用 Thread.interrupt() 机制(但 TimerTask 本身不支持中断)。

  3. 具有不可逆性

              1. 调用 cancel() 后,定时器无法重新启动。若需继续调度任务,必须创建新                              的 Timer 实例。

     4. 线程终止

  • 若 Timer 线程是 JVM 中唯一的非守护线程,调用 cancel() 后,JVM 可能退出。
  • 若 Timer 是守护线程(默认),则仅释放线程资源,不影响 JVM 运行。

场景 1:Timer 线程正在执行任务时终止

  • 操作:调用 timer.cancel()
  • 结果
    • 正在执行的任务会继续执行完毕,除非任务中响应中断(例如捕获 InterruptedException 并提前终止)。
    • 未执行的任务会被取消,不再执行。

场景 2:Timer 线程处于阻塞状态时终止

  • 操作:调用 timer.cancel()
  • 结果
    • cancel() 会向 Timer 线程发送中断(interrupt()),尝试唤醒阻塞的线程。
    • 如果线程因 sleep()wait() 等可中断阻塞被中断,会抛出 InterruptedException,任务会提前终止,未执行的任务也会被取消。
    • 如果线程因不可中断的阻塞(如同步锁竞争)被阻塞,interrupt() 不会立即生效,线程会继续阻塞,直到阻塞解除后才会检测到中断状态,此时任务可能继续执行提前终止(取决于代码是否处理中断)。
  1. 替代方案(推荐)

    • Timer 是早期 API,存在单线程执行、异常影响调度等问题。

    • 现代 Java 推荐使用 ScheduledThreadPoolExecutor(支持线程池、更灵活的任务控制)。

3. 定时器的实现

每个线程都带有 dalay 时间,队首元素放时间小的,检查队首时间是否到时间即可

java 标准库中提供了PriorityBorinkingQueue(线程安全)和PriorityQueue(线程不安全)手动加锁控制

定时器的构成

• ⼀个带优先级队列(不要使⽤ PriorityBlockingQueue, 容易死锁!)

• 队列中的每个元素是⼀个 Task 对象.

• Task 中带有⼀个时间属性, 队⾸元素就是即将要执⾏的任务

• 同时有⼀个 t 线程⼀直扫描队⾸元素, 看队⾸元素是否需要执⾏

import java.util.PriorityQueue;
//定时任务类,表示一个待执行的任务
class MyTimerTask implements Comparable<MyTimerTask>{private long time;//执行任务时间,ms级时间戳private Runnable runnable;//记录任务要执行的代码/*** 构造函数* @param runnable 要执行的任务* @param delay 延迟时间(毫秒)*/public MyTimerTask(Runnable runnable,long delay){if(runnable == null){throw new NullPointerException("任务为空");}if (delay < 0)throw new IllegalArgumentException("延迟时间不可能为负数");this.runnable = runnable;time = System.currentTimeMillis()+delay;//绝对执行时间}//执行任务public void run(){runnable.run();}//获取执行任务时间public long getTime(){return time;}//比较方法,用于优先级队列排序public int compareTo(MyTimerTask o1){return Long.compare(this.time , o1.time);}
}
//自定义定时器类
class MyTimer{private Thread t = null;// 工作线程private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();// 任务队列private Object object = new Object();//创建锁对象private volatile boolean isRunning = true;//定时器运行状态标志//终止定时器public void cancel(){isRunning = false;queue.clear();// 清空任务队列//结束t线程t.interrupt();}//任务调度public void schedule(Runnable runnable,long delay){synchronized (object){if(!isRunning){throw new IllegalStateException("定时器已关闭");}MyTimerTask task = new MyTimerTask(runnable,delay);queue.offer(task);object.notify();//唤醒}}// 构造方法,创建扫描线程,让扫描线程来完成判定和执行public MyTimer(){t = new Thread(()->{while (isRunning){try{synchronized (object){while (isRunning && queue.isEmpty()){//队列空,阻塞object.wait();}if(!isRunning){break;// 定时器已关闭}//获取头任务MyTimerTask task = queue.peek();while (task.getTime() <= System.currentTimeMillis()){//时间未到,阻塞//阻塞delay 时间后,自动唤醒object.wait(task.getTime()-System.currentTimeMillis());}else{queue.poll();task.run();}}}catch (InterruptedException e){// 被中断时,检查是否是因cancel()调用if (!isRunning) {System.out.println("定时器工作线程正常退出");break; // 正常终止}// 如果是意外中断,恢复中断状态Thread.currentThread().interrupt();System.err.println("工作线程被意外中断");break;}}});t.start();}
}
public class Test3 {public static void main(String[] args) throws InterruptedException {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务1");}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务2");}},1000);Thread.sleep(4000);timer.cancel();}
}

4. 线程池

如果频繁创建和销毁线程开销是非常大的

1. 轻量级线程(纤程/协程)

java 21 引入 虚拟线程(Virtual Threads)

协程本质,是程序员在用户态中进行调度,不是靠内核中的调度器调度的

虚拟线程由 JVM 管理,依附于底层的操作系统线程(载体线程),创建成本极低(约 KB 级别)。

2. 线程池(Thread Pool) 是一种多线程处理模式,通过提前创建并管理一组线程,避免频繁创建和销毁线程的开销,从而提高系统性能和资源利用率。

1. 核心原理

  • 线程复用:线程池创建后,线程不会立即销毁,而是重复执行任务。
  • 任务队列:当线程池中的线程都在繁忙时,新任务会被暂存到队列中,等待线程空闲。
  • 动态管理:根据负载自动调整线程数量,避免资源浪费。

2. 优势

  1. 降低资源消耗:减少线程创建 / 销毁的开销(每个线程创建约需消耗 1MB 栈内存)。
  2. 提高响应速度:任务到达时无需等待线程创建,直接由空闲线程处理。
  3. 控制并发数量:通过设置线程池大小,避免因线程过多导致的内存溢出或 CPU 过度切换。
  4. 统一管理:提供线程监控、异常处理等机制,便于系统调优
public ThreadPoolExecutor(int corePoolSize,       // 核心线程数(最小线程数,即使空闲也不销毁)int maximumPoolSize,    // 最大线程数(核心线程+临时线程的总数上限)long keepAliveTime,     // 临时线程的存活时间(当线程数 > 核心数时,空闲线程的最大存活时间)TimeUnit unit,          // 存活时间单位BlockingQueue<Runnable> workQueue, // 任务队列(存储待执行的任务)ThreadFactory threadFactory, // 线程工厂(自定义线程创建逻辑)RejectedExecutionHandler handler // 拒绝策略(任务队列满且线程数达上限时的处理方式)
)
参数描述
corePoolSize核心线程数,线程池的 “常驻线程”,默认情况下会一直存活(除非设置 allowCoreThreadTimeOut)。
maximumPoolSize线程池能创建的最大线程数,核心线程 + 临时线程的总数不能超过该值。
keepAliveTime临时线程(超过核心线程数的部分)的空闲存活时间,超时后会被销毁。
workQueue待执行任务队列,常用类型包括:
ArrayBlockingQueue(有界队列)
LinkedBlockingQueue(无界队列)
SynchronousQueue(直接移交队列)
threadFactory用于创建线程,可自定义线程名称、守护线程等属性,便于调试。
handler拒绝策略,常用策略包括:
AbortPolicy(抛出异常,默认
CallerRunsPolicy(任务由调用者线程处理)
DiscardPolicy(丢弃新任务)
DiscardOldestPolicy(丢弃队列中最旧的任务)

1. 工厂模式

定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。

工厂模式的核心角色

  1. 抽象产品(Product)
    定义所有具体产品的公共接口或抽象类,约束产品的行为。
  2. 具体产品(Concrete Product)
    实现抽象产品接口,是实际创建的对象。
  3. 抽象工厂(Factory)
    定义创建产品的公共接口(如createProduct()方法)。
  4. 具体工厂(Concrete Factory)
    实现抽象工厂接口,负责创建具体产品对象。

Executors 是 Java 并发包 (java.util.concurrent) 中提供的一个线程池工厂类,它封装了 ThreadPoolExecutor 的复杂配置过程,提供了一系列静态工厂方法来创建不同类型的线程池。

ThreadPoolExecutor 构造函数参数较多,配置复杂,Executors 通过预定义配置简化了线程池的创建过程,使开发者能够快速获得适合常见场景的线程池。

1. Executors 工厂类核心方法

1. newFixedThreadPool(int nThreads)
  • 作用:创建固定大小的线程池,核心线程数和最大线程数相等(corePoolSize = maxPoolSize = nThreads)。
  • 队列类型:使用 无界队列 LinkedBlockingQueue(容量为 Integer.MAX_VALUE)。

  • 适用场景
    适用于已知并发量、任务耗时均匀的场景(如数据库连接池),但高负载下可能导致 OOM(无界队列积压大量任务)。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Test8 {public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(2);for(int i=0;i<1000;i++){int n = i;pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务"+n+",当前线程:"+Thread.currentThread().getName());}});}}
}
2. newCachedThreadPool()
  • 作用:创建可缓存的线程池,核心线程数为 0,最大线程数为 Integer.MAX_VALUE,空闲线程存活时间为 60 秒。
  • 队列类型:使用 直接移交队列 SynchronousQueue(不存储任务,直接传递给线程)。

  • 适用场景
    适合处理大量短时间任务(如 HTTP 请求),但高并发下可能创建海量线程导致系统崩溃
3. newSingleThreadExecutor()
  • 作用:创建单线程池,核心线程数和最大线程数均为 1,保证任务按顺序执行。
  • 队列类型:使用 无界队列 LinkedBlockingQueue

  • 适用场景
    需要保证任务顺序执行、单线程处理的场景(如日志序列化写入)。
4. newScheduledThreadPool(int corePoolSize)
  • 作用:创建支持定时 / 周期性任务的线程池,核心线程数由参数指定,最大线程数为 Integer.MAX_VALUE
  • 队列类型:使用 延迟队列 DelayedWorkQueue

  • ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,内部使用 DelayedWorkQueue 处理定时任务。
线程池类型创建方式核心问题推荐指数
固定大小线程池Executors.newFixedThreadPool(n)无界队列导致 OOM❌ 不推荐
缓存线程池Executors.newCachedThreadPool()线程数无限制导致系统崩溃❌ 不推荐
单线程池Executors.newSingleThreadExecutor()无界队列导致 OOM❌ 不推荐
定时线程池Executors.newScheduledThreadPool(corePoolSize)需注意最大线程数和队列容量✅ 谨慎使用

2、Executors 封装的优缺点

优点
  1. 简单易用:无需手动设置 ThreadPoolExecutor 的复杂参数,一行代码创建线程池。
  2. 标准化场景:针对常见场景(固定线程数、单线程、定时任务)提供预定义配置。
缺点
  1. 内存风险
    • newFixedThreadPool 和 newSingleThreadExecutor 使用无界队列 LinkedBlockingQueue,高负载下可能导致 OOM(Out Of Memory)
    • newCachedThreadPool 的最大线程数为 Integer.MAX_VALUE,可能创建过多线程耗尽系统资源。
  2. 缺乏灵活性
    无法自定义线程工厂、拒绝策略等关键参数,难以应对复杂业务场景。
  3. 性能隐患
    无界队列和默认拒绝策略(AbortPolicy)可能导致任务积压或异常抛出,影响系统稳定性。

2. 参数调优建议

1. 根据任务类型设置线程数

  • CPU 密集型corePoolSize = CPU核心数 + 1(充分利用 CPU 资源)。
  • IO 密集型corePoolSize = 2 * CPU核心数(允许更多线程等待 IO)。

1、CPU 密集型(CPU-bound)

  • 特征:任务的大部分时间用于CPU 计算(如复杂算法、数学运算、加密解密等),CPU 利用率高,而 I/O(磁盘 / 网络读写)操作较少或耗时较短。
  • 瓶颈:CPU 计算能力成为性能瓶颈,任务执行时间主要受限于 CPU 的处理速度。

2、IO 密集型(IO-bound)

  • 特征:任务的大部分时间用于等待 I/O 操作完成(如磁盘读写、网络请求、数据库查询等),CPU 利用率较低,I/O 操作的延迟成为性能瓶颈。
  • 瓶颈:I/O 设备的吞吐量或延迟(如磁盘转速、网络带宽)限制了任务执行效率。
维度CPU 密集型IO 密集型
主要耗时点CPU 计算I/O 操作(等待磁盘 / 网络)
CPU 利用率高(接近 100%)低(通常低于 50%)
理想线程数CPU 核心数 + 1更高(如 CPU 核心数 × 2 或更多)
优化重点算法效率、多核并行减少 I/O 次数、异步化、缓存
典型工具并行计算框架(如 Fork/Join)异步框架(如 Netty、OKHttp)

2. 使用有界队列:优先选择 ArrayBlockingQueue 或 LinkedBlockingQueue(capacity),避免无界队列的内存风险。

3. 自定义拒绝策略:根据业务需求选择 CallerRunsPolicy(调用者处理)或 DiscardOldestPolicy(丢弃旧任务),而非默认的 AbortPolicy

3. 简单线程池的实现(固定线程数目)

  1. 定义任务队列:使用有界阻塞队列存储待执行任务。
  2. 创建工作线程:通过构造函数初始化固定数量的线程,每个线程循环从队列中取任务执行。
  3. 提交任务:将任务放入队列,队列满时阻塞等待。
  4. 执行任务:工作线程取出任务并执行。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPool{// 任务队列:使用有界队列存储待执行的任务,容量为1000private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);// n 表⽰线程池⾥有⼏个线程.// 创建了⼀个固定数量的线程池public MyThreadPool(int n){for(int i =0;i<n;i++){Thread t = new Thread(()->{while (true){try {//队列空,阻塞Runnable runnable = queue.take();//取出任务runnable.run();//执行} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();}}//将任务提交到线程池public void submit(Runnable runnable) throws InterruptedException {//队列满,阻塞queue.put(runnable);//添加任务}
}
public class Test {public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool(4);for(int i=0;i<1000;i++){int n = i;myThreadPool.submit(new Runnable() {@Overridepublic void run() {System.out.println("执行任务"+n+",当前线程为:"+Thread.currentThread().getName());}});}}
}

相关文章:

  • 使用 Navicat 工具管理时,点击某一列,能否查看该列的平均值和最大值等关联信息?
  • 【前端部署】通过 Nginx 让局域网用户访问你的纯前端应用
  • SSH漏洞修复方案
  • GitHub 趋势日报 (2025年05月19日)
  • 机器学习第十九讲:交叉验证 → 用五次模拟考试验证真实水平
  • DataLight(V1.7.12)版本更新发布
  • 进程间通信(IPC):LocalSocket
  • ES(Elasticsearch) 基本概念(一)
  • 开疆智能Profinet转RS485网关连接电磁流量计到西门子PLC配置案例
  • WD5030L CC/CV模式DCDC15A高效同步转换器消费电子工业控制汽车电子优选择
  • Linux X86平台安装ARM64交叉编译器方法
  • LLM大模型工具链
  • MySQL与Redis一致性问题分析
  • 4大AI智能体平台,你更适合哪一个呐?
  • 单端传输通道也会有奇偶模现象喔
  • Dockerfile 实战:编写高效镜像的最佳实践与常见误区
  • 算法与数据结构:位运算与快速幂
  • python实战项目70:如何给一个空的DataFrame添加行
  • Vue 3.0 Transition 组件使用详解
  • 软件测试期末复习
  • 建行原副行长章更生涉嫌受贿罪、违法发放贷款罪被逮捕
  • 换灯如换脸!西安碑林整修重开观展体验提升
  • 西岸大剧院夏秋演出季公布,阿云嘎制作《风声》9月驻演
  • 围绕“工程智能”系统布局,同济大学官宣成立五大研究院
  • 凤阳文旅局长回应鼓楼瓦片脱落:楼宇是否属于文物?施工经费用在何处?
  • 菲律宾华人“钢铁大王”撕票案两主谋落网,部分赎金已被提取