Java中的线程池

一、自定义线程池

package org.example.n8;

import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.TestPool")
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1,
                (queue, task)->{
                    // 1)死等
//                    queue.put( task);
                    // 2)带超时等待
//                    queue.offer( task, 500, TimeUnit.MILLISECONDS);
                    // 3)让调用者放弃任务执行
//                    log.debug("放弃任务:{}", task);
                    // 4)让调用者抛出异常
//                    throw new RuntimeException("任务执行失败:" + task);
                    // 5)让调用者自己执行任务
                    task.run();
                });
        for (int i = 0; i < 4; i++) {
            int j= i;
            threadPool.execute(() -> {
                try {
                    Thread.sleep(1500L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.debug("{}", j);
            });
        }
    }
}

@FunctionalInterface
interface RejectPolicy<T>{
    void reject(BlockingQueue<T> queue, T task);
}




@Slf4j(topic = "c.ThreadPool")
class ThreadPool {
    // 任务队列
    private BlockingQueue<Runnable> taskQueue;

    // 线程集合
    private HashSet<Worker> workers = new HashSet<>();

    // 核心线程数
    private int coreSize;

    // 获取线程的超时时间
    private long timeout;
    private TimeUnit timeUnit;

    // 拒绝策略
    private RejectPolicy<Runnable> rejectPolicy;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int capacity, RejectPolicy<Runnable> rejectPolicy) {
        taskQueue = new BlockingQueue<>(capacity);
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.rejectPolicy = rejectPolicy;
    }

    public void execute(Runnable task) {
        // 当任务数没有达到核心线程数时,新创建一个线程
        synchronized ( workers){
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.debug("新增worker:{}, {}", worker, task);
                workers.add(worker);
                worker.start();
            } else {
                // 添加任务
//                taskQueue.put(task);
                // 1)死等
                // 2)待超时等待
                // 3)让调用者放弃任务执行
                // 4)让调用者抛出异常
                // 5)让调用者自己执行任务
                taskQueue.tryPut(rejectPolicy, task);
            }
        }
    }

    class Worker extends Thread{
        private Runnable task;
        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            // 执行任务
            // 1)当task不为null,执行任务
            // 2)当task执行完毕,从任务队列中获取任务
//            while (task != null || (task = taskQueue.take()) != null) {
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {

                    log.debug("正在执行任务.....{}", task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }
            synchronized ( workers){
                log.debug("worker被移除:{}", this);
                workers.remove(this);
            }
        }
    }
}

@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T>{
    // 1.任务队列
    private Deque<T> queue = new ArrayDeque<>();

    // 2.锁,用于在任务队列中锁住对头和队尾
    private ReentrantLock lock = new ReentrantLock();

    // 3.生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    // 4.消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();

    // 5.容量
    private int capacity;
    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    // 超时的阻塞获取
    public T poll(long timeout, TimeUnit unit) {
        lock.lock();
        try {
            // 将时间统一转化为毫秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()) {
                try {
                    nanos = emptyWaitSet.awaitNanos(nanos);
                    if (nanos <= 0) {
                        return null;
                    }
                    emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }

    // 阻塞获取
    public T take() {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }

    // 阻塞添加
    public void put(T task) {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                try {
                    log.debug("等待加入任务队列:{}", task);
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("加入任务队列:{}", task);
            queue.addLast(task);
            emptyWaitSet.signal();
        }finally {
            lock.unlock();
        }
    }


    // 带超时时间的阻塞式添加
    public boolean offer(T task, long timeout, TimeUnit unit) {
        lock.lock();
        try {
            long nanos = unit.toNanos(timeout);
            while (queue.size() == capacity) {
                try {
                    log.debug("等待加入任务队列:{}", task);
                    if(nanos <= 0){
                        return false;
                    }
                    nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("加入任务队列:{}", task);
            queue.addLast(task);
            emptyWaitSet.signal();
            return true;
        }finally {
            lock.unlock();
        }
    }

    public int size() {
        lock.lock();
        try {
            return queue.size();
        }finally {
            lock.unlock();
        }
    }

    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否已满
            if (queue.size() == capacity) {
                rejectPolicy.reject(this, task);
            }else { // 有空闲
                log.debug("加入任务队列:{}", task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        }finally {
            lock.unlock();
        }
    }
}

二、线程池

1.两种线程池

ThreadPoolExecutor:线程池

ScheduleThreadPoolExecutor:带调度功能的线程池

2. ThreadPoolExecutor 线程池

2.1线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值

线程池的5中状态:

RUNNING:运行状态,线程池创建好之后就会进入此状态,如果不手动调用关闭方法,那么线程池在整个程序运行期间都是此状态。

SHUTDOWN:关闭状态,不再接受新任务提交,但是会将已保存在任务队列中的任务处理完。

STOP:停止状态,不再接受新任务提交,并且会中断当前正在执行的任务、放弃任务队列中已有的任务。

TIDYING:整理状态,所有的任务都执行完毕后(也包括任务队列中的任务执行完),当前线程池中的活动线程数降为 0 时的状态。到此状态之后,会调用线程池的 terminated() 方法。

TERMINATED:销毁状态,当执行完线程池的 terminated() 方法之后就会变为此状态。

2.2构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
线程池构造方法的核心参数:
        corePoolSize 核心线程数目 (最多保留的线程数)
        maximumPoolSize 最大线程数目
        keepAliveTime 生存时间 - 针对救急线程
        unit 时间单位 - 针对救急线程
        workQueue 阻塞队列
        threadFactory 线程工厂 - 可以为线程创建时起个好名字
        handler 拒绝策略

线程池的工作方式:

1.线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
2.当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排 队,直到有空闲的线程。
3.如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize -corePoolSize 数目的线 程来救急。
4.如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它 著名框架也提供了实现。
        AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略了。
        CallerRunsPolicy 让调用者运行任务
        DiscardPolicy 放弃本次任务
        DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
        Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方 便定位问题
        Netty 的实现,是创建一个新线程来执行任务
        ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
        PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
5.当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime unit 来控制。

2.3JDK提供的线程池,通过Executors工具类新建

2.3.1.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

特点:

  • 核心线程数 = 最大线程数 = nThreads(固定线程数量,无临时线程)。
  • 空闲线程不会被销毁(keepAliveTime = 0)。
  • 任务队列是 LinkedBlockingQueue(无界队列)

用途:

适用于任务数量已知、耗时相对均匀的场景,如服务器接收固定数量的并发请求(避免线程频繁创建 / 销毁的开销)。

  • 示例:处理数据库连接池的并发查询(线程数可设为 CPU 核心数的 2~4 倍)。

注意:

无界队列可能导致任务堆积过多,引发 OOM(内存溢出),需控制任务提交速度。

2.3.2.newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

特点:

  • 核心线程数 = 0最大线程数 = Integer.MAX_VALUE(理论上可创建无限多临时线程)。
  • 临时线程空闲 60 秒后自动销毁keepAliveTime = 60s)。
  • 任务队列是 SynchronousQueue(同步队列,不存储任务,直接传递给线程)

用途:

适用于任务数量不确定、耗时短、并发峰值高的场景,如临时突发的短任务(快速处理后释放资源)。

  • 示例:Web 服务器处理瞬时高并发的短请求(如查询接口)。

注意:

最大线程数无上限,若任务执行时间过长,可能创建大量线程导致 CPU / 内存耗尽,需谨慎使用。

SynchronousQueue,同步队列容量为0,也就是说想队列中添加元素的时候,如果此时没有线程消费元素,添加元素的线程会阻塞。

2.3.3.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
使用场景:

希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程 也不会被释放。

区别:

        自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一 个线程,保证池的正常工作

        Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改

        FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因 此不能调用 ThreadPoolExecutor 中特有的方法

        Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改

2.4提交任务

// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
 throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
 long timeout, TimeUnit unit)
 throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
 throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
 long timeout, TimeUnit unit)
 throws InterruptedException, ExecutionException, TimeoutException;

2.5关闭线程池

ThreadPoolExecutor 提供了两个核心关闭方法:shutdown() 和 shutdownNow(),以及一个辅助方法 awaitTermination() 用于等待关闭完成。

2.5.1shutdown()方法

线程状态由RUNNING变为SHUTDOWN

核心行为

  • 拒绝接收新任务(新任务提交会触发 RejectedExecutionException)。
  • 继续执行正在运行的任务等待队列中已提交的任务
  • 所有任务执行完毕后,自动终止所有工作线程,线程池状态变为 TERMINATED

适用场景:希望等待已有任务(包括队列中的任务)完成后再关闭,适用于允许一定关闭时间的场景(如应用正常停机)。

代码事例

package org.example.n8;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "c.TestShutDown")
public class TestShutDown {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(() -> {
            log.debug("Task1 begin---- ");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("Task1 end---- ");
        });

        executorService.execute(() -> {
            log.debug("Task2 begin---- ");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("Task2 end---- ");
        });

        executorService.execute(() -> {
            log.debug("Task3 begin---- ");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("Task3 end---- ");
        });
        log.debug("shutdown----");
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.SECONDS);
        log.debug("other----");
        
    }
}

2.5.2shutdownNow()方法

线程状态由RUNNING变为STOP

核心行为

  • 拒绝接收新任务。
  • 尝试中断正在运行的任务(通过 Thread.interrupt(),但仅对可中断的任务有效,如 sleep()wait() 等阻塞操作)。
  • 清空等待队列,返回未执行的任务列表(可用于记录或重试)。
  • 尽快终止所有工作线程,线程池状态最终变为 TERMINATED

注意

  • 中断正在运行的任务不一定能立即停止(若任务不响应中断,可能继续执行)。
  • 未执行的任务会被从队列中移除,需手动处理(如记录日志、后续重试)。

适用场景:需要立即关闭线程池,不等待队列任务(如应用紧急停机、任务超时强制终止)。

当线程池调用 shutdownNow() 后,若存在未处理中断逻辑的线程(即线程持续执行任务,不响应中断),那么该线程依然后执行,那么线程池处在stop状态,若该线程一直不结束,则线程池会一直处于stop状态。

2.6异步模式之工作线程

工作线程模式是异步模式的 “实现方案之一”,异步模式是 “设计思想”,工作线程模式是 “落地工具”。

异步模式(设计思想)

异步模式是一种任务执行与结果获取分离的设计思想:

  • 发起任务后,调用方无需阻塞等待任务完成,可以继续执行其他操作;
  • 任务执行完成后,通过回调函数、Future、事件通知等方式,将结果反馈给调用方(或直接处理结果);
  • 核心目标:提高系统吞吐量(避免线程阻塞浪费资源),提升响应速度。
工作线程实现方案

工作线程模式(也叫 “线程池模式”)是一种基于线程池的任务分发与执行机制

  • 提前创建一组固定 / 动态的 “工作线程”(Worker Thread),并维护一个任务队列;
  • 主线程(或任务提交线程)仅负责将任务放入队列,不直接执行任务;
  • 工作线程持续从队列中获取任务并执行,执行完成后无需销毁,继续等待下一个任务;
  • 核心目标:避免线程频繁创建 / 销毁的开销,控制并发数,提高任务执行效率。

示例:

  • 餐厅的 “后厨团队”(工作线程池),前台服务员(主线程)接到订单(任务)后,只需将订单放入后厨队列,无需自己做菜;
  • 后厨厨师(工作线程)轮流取订单做菜,做完一道再取下一道,这就是工作线程模式。
二者的关系:工作线程模式是异步模式的 “最优解” 之一

异步模式的核心是 “不阻塞调用方”,而工作线程模式恰好能高效实现这一点,原因如下:

  1. 调用方无阻塞:任务提交方(如主线程)将任务放入队列后立即返回,无需等待任务执行,符合异步 “非阻塞” 的核心要求;
  2. 任务并发执行:工作线程池中的多个线程并行处理任务,提高了任务执行效率,契合异步模式 “提高吞吐量” 的目标;
  3. 资源可控:通过线程池控制最大并发数,避免因创建大量线程导致的资源耗尽,让异步任务的执行更稳定。

典型场景:

  • 后端接口处理异步任务(如发送短信、生成报表):接口接收请求后,将任务提交给线程池(工作线程模式),立即返回 “任务已受理”(异步响应),线程池后台执行任务,执行完成后通过消息队列或数据库记录结果;
  • 前端异步请求:浏览器发起 AJAX 请求(异步模式),后端服务器通过线程池(工作线程模式)处理请求,避免主线程阻塞。

2.7线程池创建多少线程合适

2.7.1CPU密集型任务

特点:任务主要消耗 CPU 资源(如数据计算、排序、加密),线程几乎不阻塞(IO 耗时占比 < 10%)。

核心原则:线程数 ≈ CPU 核心数,避免线程切换开销(切换一次耗时约 1-10 微秒,频繁切换会抵消多线程优势)。

计算公式线程数 = CPU 核心数 ± 1(+1 是为了应对单个线程偶尔的阻塞,避免 CPU 空闲)。

示例:8 核 CPU 服务器 → 线程数设为 8 或 9。

2.7.2IO 密集型任务

特点:任务大部分时间在等待 IO 操作(如数据库查询、网络请求、文件读写),线程阻塞时间长(IO 耗时占比 > 50%)。

核心原则:线程数 > CPU 核心数,利用 IO 等待时间让其他线程执行任务,提高 CPU 利用率。

计算公式线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间

示例4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 100% 利用,套用公式

4 * 100% * 100% / 50% = 8
2.7.3混合任务(既有 CPU 计算也有 IO 等待)

核心原则:先拆分任务类型,分别按上述公式计算,再取折中值;或通过压测调整。

简化方案:按 IO 密集型公式计算(因混合任务中 IO 等待通常占比更高),再根据实际压测结果微调。

2.7.4关键影响因素(实际调整依据)

公式是基础,生产环境需结合以下因素修正

  1. CPU 核心数(物理核心,非逻辑核心)

    • 查看方式:Linux 执行 lscpu(看 CPU(s): 对应的物理核心数,排除超线程);Windows 任务管理器 → 性能 → CPU → 核心数。
    • 注意:超线程(HT)会让逻辑核心数翻倍,但对 CPU 密集型任务提升有限,线程数仍按物理核心计算。
  2. 任务的 IO 等待占比

    • 若 IO 等待时间极长(如调用第三方接口耗时 1 秒,CPU 执行仅 10 毫秒),可进一步增加线程数(如 CPU 核心数 × 5~10),但需控制上限(避免线程过多导致内存溢出)。
    • 可通过压测工具(如 JMeter)统计 “线程阻塞时间” 和 “执行时间”,精准计算比例。
  3. 服务器其他进程的 CPU 占用

    • 若服务器同时运行数据库、缓存等其他服务,需预留 20%-30% CPU 资源,线程数 = (可用 CPU 核心数)× 公式系数。
    • 示例:8 核 CPU 中,数据库占用 4 核 → 可用核心数按 5 核计算。
  4. 线程池的任务队列大小

    • 线程数与队列大小需配合:IO 密集型任务队列可设为中等大小(如 100-1000),避免队列过长导致任务堆积;CPU 密集型任务队列可设为较小值(如 10-50),让线程快速处理任务。
  5. 内存限制

    • 每个线程默认栈大小为 1MB(可通过 -Xss 调整),线程数过多会消耗大量内存(如 1000 个线程 ≈ 1GB 栈内存)。
    • 需确保线程数 × 线程栈大小 + 任务内存占用 ≤ 服务器可用内存,避免 OOM。
2.7.5生产环境推荐步骤(落地流程)
  1. 第一步:获取基础参数

    • 服务器物理 CPU 核心数(记为 N)。
    • 任务类型(判断是 CPU 密集型还是 IO 密集型)。
  2. 第二步:初步计算线程数

    • CPU 密集型:N ± 1。
    • IO 密集型:N × 2 ~ N × 4。
  3. 第三步:压测验证与调整

    • 用压测工具(JMeter、Gatling)模拟生产并发量,观察指标:
      • CPU 利用率:目标 70%-80%(过高会导致系统响应变慢,过低说明线程数不足)。
      • 任务响应时间:是否在可接受范围内(如接口响应 < 500ms)。
      • 线程池队列堆积:是否有大量任务排队(队列长度持续增长说明线程数不足,需增加)。
    • 调整规则:
      • CPU 利用率 < 50% + 任务排队 → 增加线程数。
      • CPU 利用率 > 90% + 响应时间变长 → 减少线程数。
      • 内存占用过高 → 减少线程数或调小线程栈大小(-Xss512k)。
  4. 第四步:预留冗余

    • 最终线程数 = 压测最优值 × 0.8(预留 20% 资源应对流量峰值)。

2.8任务调度线程池

2.8.1.Timer

在任务调度线程池功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用,但 由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个 任务的延迟或异常都将会影响到之后的任务。

核心功能

Timer 的核心作用是按计划执行任务,支持两种调度方式:

  1. 一次性任务:在指定的延迟时间后执行一次(如 3 秒后执行某个任务)。
  2. 周期性任务:在指定延迟后开始,之后按固定间隔重复执行(如延迟 1 秒后,每 5 秒执行一次)。

核心组件

Timer 的使用依赖两个核心类:

  • Timer:负责调度和管理任务,内部维护一个后台线程(TimerThread)和一个优先级任务队列(TaskQueue),按时间顺序执行任务。
  • TimerTask:抽象类,代表要执行的任务,需要重写 run() 方法定义任务逻辑,实现了 Runnable 接口。

常用方法

Timer 提供了调度任务的关键方法,主要分为两类:

调度一次性任务

// 在 delay 毫秒后执行 task
void schedule(TimerTask task, long delay);

// 在指定时间 time 执行 task(若 time 已过去,则立即执行)
void schedule(TimerTask task, Date time);

调度周期性任务

// 延迟 delay 毫秒后,每隔 period 毫秒重复执行 task(以上一次任务开始时间为基准)
void schedule(TimerTask task, long delay, long period);

// 在指定时间 time 开始,之后每隔 period 毫秒重复执行 task
void schedule(TimerTask task, Date time, long period);

// 延迟 delay 毫秒后,每隔 period 毫秒重复执行 task(以上一次任务结束时间为基准)
void scheduleAtFixedRate(TimerTask task, long delay, long period);

schedule 与 scheduleAtFixedRate 的区别

  • schedule:周期以 “上一次任务开始时间” 为基准(若任务执行时间超过周期,下一次会立即执行)。
  • scheduleAtFixedRate:周期以 “上一次任务结束时间” 为基准(严格保证固定间隔,若任务执行超时,会追赶执行以弥补延迟)。

使用示例

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
    public static void main(String[] args) {
        Timer timer = new Timer(); // 创建 Timer 实例(启动后台线程)

        // 1. 一次性任务:3 秒后执行
        TimerTask oneTimeTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("一次性任务执行:" + System.currentTimeMillis());
            }
        };
        timer.schedule(oneTimeTask, 3000); // 延迟 3000 毫秒(3 秒)

        // 2. 周期性任务:延迟 1 秒后,每 2 秒执行一次
        TimerTask periodicTask = new TimerTask() {
            private int count = 0;
            @Override
            public void run() {
                count++;
                System.out.println("周期性任务执行(第 " + count + " 次):" + System.currentTimeMillis());
                if (count >= 3) {
                    this.cancel(); // 执行 3 次后取消当前任务
                    timer.cancel(); // 取消 Timer(停止所有任务)
                }
            }
        };
        timer.schedule(periodicTask, 1000, 2000); // 延迟 1 秒,周期 2 秒
    }
}

执行结果

周期性任务执行(第 1 次):1696300000000
一次性任务执行:1696300002000
周期性任务执行(第 2 次):1696300002000
周期性任务执行(第 3 次):1696300004000

底层原理

Timer 的工作机制如下:

  1. Timer 实例创建时,会启动一个后台线程(TimerThread),该线程循环读取任务队列(TaskQueue)。
  2. 任务队列按任务的 “下次执行时间” 排序(优先级队列),TimerThread 会阻塞等待直到下一个任务的执行时间。
  3. 到达执行时间后,TimerThread 取出任务并执行其 run() 方法,然后更新周期性任务的下次执行时间并重新加入队列。

优缺点与适用场景

    优点

  • 用法简单,适合轻量级、低频率的定时任务(如简单的延迟操作、周期性日志打印)。
  • 无需依赖第三方库,JDK 原生支持。

    缺点

  • 单线程执行:所有任务由同一个后台线程执行,若某个任务执行时间过长(如阻塞),会阻塞后续所有任务(导致延迟)。
  • 不支持并发:无法利用多核 CPU,任务间相互影响(一个任务异常会导致整个 Timer 崩溃)。
  • 时间精度较低:依赖系统时间和线程调度,不适用于高精度定时场景。
  • 任务取消机制有限:TimerTask.cancel() 仅取消单个任务,Timer.cancel() 会终止所有任务,但可能存在线程安全问题。

    适用场景

  • 简单的定时需求(如延迟几秒后关闭资源、低频率的周期性检查)。
  • 对并发和时间精度要求不高的场景。
2.8.2newScheduledThreadPool

java.util.concurrent.Executors.newScheduledThreadPool(int corePoolSize) 是 Java 提供的定时任务线程池,专门用于调度延迟任务或周期性任务,底层基于 ScheduledThreadPoolExecutor 实现(继承自 ThreadPoolExecutor),支持多线程并发执行定时任务,解决了 Timer 单线程阻塞的问题。

2.8.2.1.newScheduledThreadPool 线程池的核心特性

核心线程数固定:通过参数 corePoolSize 指定核心线程数(不会被回收),非核心线程(若有)在空闲时会被回收(默认空闲时间 10 秒)。

支持定时任务:提供延迟执行、周期性执行两种调度方式,任务按时间顺序加入队列,由线程池中的线程并发执行。

基于延迟队列:内部使用 DelayedWorkQueue(延迟队列)存储任务,按任务的 “下次执行时间” 排序,确保任务在指定时间被触发。

多线程并发:多个任务可由不同线程并行执行,避免单线程阻塞(区别于 Timer)。

2.8.2.2.核心调度方法

newScheduledThreadPool 返回 ScheduledExecutorService 接口,该接口提供 4 个核心调度方法,按功能分为 “延迟执行” 和 “周期性执行” 两类:

延迟执行(一次性任务)

在指定延迟时间后执行一次任务,无返回值或有返回值(通过 Future 获取)。

方法签名功能描述
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)延迟 delay 时间后,执行 Runnable 任务(无返回值),返回 ScheduledFuture 用于取消任务。
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)延迟 delay 时间后,执行 Callable 任务(有返回值),返回 ScheduledFuture 用于获取结果或取消任务。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); // 核心线程数 2

// 1. 延迟 3 秒执行 Runnable 任务(无返回值)
executor.schedule(() -> {
    System.out.println("3秒后执行的任务(Runnable)");
}, 3, TimeUnit.SECONDS);

// 2. 延迟 2 秒执行 Callable 任务(有返回值)
ScheduledFuture<String> future = executor.schedule(() -> {
    System.out.println("2秒后执行的任务(Callable)");
    return "任务完成";
}, 2, TimeUnit.SECONDS);

// 获取 Callable 任务的结果(会阻塞直到任务完成)
try {
    String result = future.get();
    System.out.println("Callable 结果:" + result);
} catch (Exception e) {
    e.printStackTrace();
}

周期性执行(重复任务)

延迟指定时间后开始执行,之后按固定周期重复执行,核心区别在于周期的计算基准(以上一次任务开始时间或结束时间为准)。

方法签名功能描述
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)延迟 initialDelay 后开始执行,之后以上一次任务开始时间为基准,每隔 period 时间重复执行。若任务执行时间超过 period,下一次任务会立即执行(追赶执行)。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)延迟 initialDelay 后开始执行,之后以上一次任务结束时间为基准,每隔 delay 时间重复执行。无论任务执行多久,两次任务的间隔始终为 delay(不追赶)。
2.8.2.3.异常处理机制

定时线程池的异常处理需注意:任务执行时的异常会导致该周期性任务终止(后续不再执行),且异常默认会被封装到 ScheduledFuture 中(若未处理会被吞噬)。

异常对任务的影响

  • 一次性任务:异常仅导致当前任务失败,不影响其他任务。
  • 周期性任务:若某次执行抛出未捕获异常,该任务会被从线程池移除,后续周期不再执行(这是最常见的 “定时任务突然停止” 原因)。

处理策略(核心:主动捕获异常)

必须在任务内部用 try-catch 捕获所有异常,避免周期性任务终止,同时记录日志便于排查。

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

// 周期性任务:每 2 秒执行一次,模拟偶尔抛出异常
ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {
    try {
        System.out.println("周期性任务执行:" + LocalTime.now());
        // 模拟 50% 概率抛出异常
        if (Math.random() > 0.5) {
            throw new RuntimeException("任务执行失败");
        }
    } catch (Exception e) {
        // 关键:捕获异常并记录日志,避免任务终止
        System.err.println("任务异常,但继续执行:" + e.getMessage());
    }
}, 1, 2, TimeUnit.SECONDS);

执行结果

周期性任务执行:10:00:00
周期性任务执行:10:00:02
任务异常,但继续执行:任务执行失败
周期性任务执行:10:00:04  // 异常后仍继续执行
周期性任务执行:10:00:06
...

若未捕获异常,任务会在第一次异常后终止,不再执行。

处理 ScheduledFuture 中的异常

若任务内部未捕获异常,异常会被封装到 ScheduledFuture 中,需调用 get() 方法触发异常(适用于一次性任务):

ScheduledFuture<?> future = executor.schedule(() -> {
    throw new RuntimeException("一次性任务失败");
}, 1, TimeUnit.SECONDS);

try {
    future.get(); // 调用 get() 时,异常以 ExecutionException 抛出
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
} catch (ExecutionException e) {
    System.err.println("捕获到任务异常:" + e.getCause().getMessage());
}
2.8.2.4.关闭线程池

定时线程池的关闭方法与普通线程池一致,需调用 shutdown() 或 shutdownNow(),并建议配合 awaitTermination() 等待关闭完成:

executor.shutdown(); // 启动关闭,不再接收新任务,执行完已有任务
try {
    if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
        executor.shutdownNow(); // 超时未关闭则强制终止
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
}

3.Fork 与 Join

3.1.概念

Fork/Join JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型 运算 
所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计 算,如归并排序、斐波那契数列、都可以用分治思想进行求解
Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运 算效率
Fork/Join 默认会创建与 cpu 核心数大小相同的线程池

3.2.使用例子

package org.example.n8;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class TestForkJoin {
    public static void main(String[] args) {
        // 创建线程池对象
        ForkJoinPool pool = new ForkJoinPool(4);
        System.out.println(pool.invoke(new MyTask(5)));
    }
}

// 建立任务对象
// 若任务有返回值,则继承RecursiveTask
// 若任务无返回值,则继承RecursiveAction
@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask<Integer>{

    private int n;

    public MyTask(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "{" + n + '}';
    }
    @Override
    protected Integer compute() {
        // 如果 n 已经为 1,可以求得结果了
        if (n == 1) {
            log.debug("join() {}", n);
            return n;
        }

        // 将任务进行拆分(fork)
        MyTask t1 = new MyTask(n - 1);
        t1.fork();
        log.debug("fork() {} + {}", n, t1);

        // 合并(join)结果
        int result = n + t1.join();
        log.debug("join() {} + {} = {}", n, t1, result);
        return result;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值