在日常开发中,我们经常遇到这样一种需求:需要按照任务的优先级顺序来执行,而不是简单的先进先出(FIFO)。Java 提供了 PriorityBlockingQueue
,这是一个基于优先级排序的线程安全队列,可以用于实现一个支持优先级任务的线程池。在这篇博客中,我们将介绍两种使用 PriorityBlockingQueue
实现优先级线程池的方法,并讨论它们的适用场景和优缺点。
方法一:实现 Comparable
接口的优先级任务类
1.1 方案概述
这种方法的核心在于让任务类(Task
)实现 Comparable
接口,并在 compareTo
方法中定义优先级的排序逻辑。PriorityBlockingQueue
会根据 Comparable
的实现来自动对任务进行排序,优先级高的任务会被排在队列的前面,先执行。
1.2 实现步骤
- 定义优先级任务类:实现
Runnable
或Callable
接口,同时实现Comparable
接口。 - 创建优先级线程池:使用
PriorityBlockingQueue
作为任务队列。 - 提交任务到线程池:按不同优先级提交任务,线程池会自动按照优先级来执行任务。
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;}@Overridepublic void run() {System.out.println("Executing task: " + name + " with priority: " + priority);}@Overridepublic 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 实现步骤
- 定义任务类:实现
Runnable
或Callable
接口,无需实现Comparable
。 - 创建优先级线程池:使用
PriorityBlockingQueue
作为任务队列,并传入自定义的Comparator
。 - 提交任务到线程池:按照不同优先级提交任务,线程池会根据
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;}@Overridepublic 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
自定义排序规则更适合。