Java 中基于优先级执行任务的线程池实现

时间:2024-11-10 09:19:04

在日常开发中,我们经常遇到这样一种需求:需要按照任务的优先级顺序来执行,而不是简单的先进先出(FIFO)。Java 提供了 PriorityBlockingQueue,这是一个基于优先级排序的线程安全队列,可以用于实现一个支持优先级任务的线程池。在这篇博客中,我们将介绍两种使用 PriorityBlockingQueue 实现优先级线程池的方法,并讨论它们的适用场景和优缺点。

方法一:实现 Comparable 接口的优先级任务类

1.1 方案概述

这种方法的核心在于让任务类(Task)实现 Comparable 接口,并在 compareTo 方法中定义优先级的排序逻辑。PriorityBlockingQueue 会根据 Comparable 的实现来自动对任务进行排序,优先级高的任务会被排在队列的前面,先执行。

1.2 实现步骤

  1. 定义优先级任务类:实现 RunnableCallable 接口,同时实现 Comparable 接口。
  2. 创建优先级线程池:使用 PriorityBlockingQueue 作为任务队列。
  3. 提交任务到线程池:按不同优先级提交任务,线程池会自动按照优先级来执行任务。

1.3 示例代码

import java.util.concurrent.*;

public class PriorityTask implements Runnable, Comparable<PriorityTask> {
    private final int priority;
    private final String name;

    public PriorityTask(int priority, String name) {
        this.priority = priority;
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("Executing task: " + name + " with priority: " + priority);
    }

    @Override
    public int compareTo(PriorityTask other) {
        return Integer.compare(this.priority, other.priority); // 优先级值越小,优先级越高
    }
}

public class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
    public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, new PriorityBlockingQueue<Runnable>());
    }
}

1.4 使用示例

public class Main {
    public static void main(String[] args) {
        PriorityThreadPoolExecutor executor = new PriorityThreadPoolExecutor(2, 4, 1, TimeUnit.MINUTES);
        executor.execute(new PriorityTask(10, "Low priority task"));
        executor.execute(new PriorityTask(1, "High priority task"));
        executor.execute(new PriorityTask(5, "Medium priority task"));
        executor.shutdown();
    }
}

1.5 优缺点

  • 优点

    • 直接在任务类中定义优先级逻辑,代码结构简单明了。
    • 无需额外的排序定义,PriorityBlockingQueue 自动根据 Comparable 实现排序。
  • 缺点

    • 任务类必须实现 Comparable 接口,优先级规则是硬编码在任务类中的,难以灵活修改。
    • 如果需要多种优先级规则,任务类代码会变得复杂,不适合多变的业务需求。

方法二:使用 Comparator 自定义排序规则

2.1 方案概述

在这种方法中,我们不再让任务类实现 Comparable 接口,而是使用 PriorityBlockingQueue 的构造方法传入一个 Comparator 对象。这样可以通过 Comparator 动态地定义任务的优先级排序规则。这种方式更加灵活,适合需要动态设置优先级或多重排序规则的场景。

2.2 实现步骤

  1. 定义任务类:实现 RunnableCallable 接口,无需实现 Comparable
  2. 创建优先级线程池:使用 PriorityBlockingQueue 作为任务队列,并传入自定义的 Comparator
  3. 提交任务到线程池:按照不同优先级提交任务,线程池会根据 Comparator 的排序规则来执行任务。

2.3 示例代码

import java.util.concurrent.*;

public class Task implements Runnable {
    private final int priority;
    private final String name;

    public Task(int priority, String name) {
        this.priority = priority;
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("Executing task: " + name + " with priority: " + priority);
    }

    public int getPriority() {
        return priority;
    }
}

public class PriorityThreadPoolExecutor extends ThreadPoolExecutor {
    public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit,
              new PriorityBlockingQueue<>(11, Comparator.comparingInt(Task::getPriority)));
    }
}

2.4 使用示例

public class Main {
    public static void main(String[] args) {
        PriorityThreadPoolExecutor executor = new PriorityThreadPoolExecutor(2, 4, 1, TimeUnit.MINUTES);
        executor.execute(new Task(10, "Low priority task"));
        executor.execute(new Task(1, "High priority task"));
        executor.execute(new Task(5, "Medium priority task"));
        executor.shutdown();
    }
}

2.5 优缺点

  • 优点

    • 可以灵活定义优先级规则,通过传入不同的 Comparator 实现动态排序。
    • 更加清晰的结构,不需要在任务类中嵌入优先级排序逻辑,任务类更加独立。
  • 缺点

    • 相对复杂一些,需要定义额外的 Comparator
    • 可能需要管理多个 Comparator 实现,如果优先级规则过多,可能会增加一定的代码复杂度。

两种方法对比总结

比较项 方法一:实现 Comparable 方法二:使用 Comparator 自定义排序规则
代码结构 任务类中包含优先级逻辑 任务类和优先级规则分离
灵活性 不灵活,排序规则固定 灵活,可随时更改 Comparator
适用场景 固定优先级规则 动态优先级需求,多种排序规则
实现难度 简单,适合初学者 略复杂,需要理解 Comparator 使用
扩展性 差,需改动任务类本身 高,可复用不同 Comparator

总结

  • 如果您的任务优先级是固定的,而且排序规则简单,方法一:实现 Comparable 接口 更加直接明了。
  • 如果您需要灵活的优先级规则,或者希望在不同条件下使用不同的优先级排序规则,那么方法二:使用 Comparator 自定义排序规则更适合。