Java的优先级任务队列的实践
队列的基本理解
在说队列之前说两个名词:Task
是任务,TaskExecutor
是任务执行器
而我们今天要说的队列就完全符合某机构这个情况,队列在有Task
进来的时候TaskExecutor
就立刻开始执行Task
,当没有Task
的时候TaskExecutor
就处于一个阻塞状态,当有很多Task的时候Task也需要排队,TaskExecutor
也可以是多个,并且可以指定某几个Task
优先执行或者滞后执行。
综上所说我们得出一个这样的关系:队列相当于某机构,TaskExecutor相当于窗口,办事者
就是Task
。
普通队列
当然很多机构也没有设置什么军人优先的窗口,所以队列也有不带优先级的队列,因此我们先来实现一个非优先级的队列。
和上述某机构不一样,某机构可以先有机构,再有窗口,再有办事者。但是我们写代码的时候,要想写一个队列,那么务必要在队列中写TaskExecutor
,那么就得先写好TaskExecutor
类,以此类推就得先有Task
类。
因此我们先写一个Task
的接口,也就是办事的人,我把它设计为接口,方便办各种不同事的人进来:
- // 办事的人。
- public interface ITask {
- // 办事,我们把办事的方法给办事的人,也就是你要办什么事,由你自己决定。
- void run();
- }
接下来再写一个TaskExecutor
的类,也就是窗口,用来执行Task
,认真看注释,非常有助于理解:
- // 窗口
- public class TaskExecutor extends Thread {
- // 在窗口拍的队,这个队里面是办事的人。
- private BlockingQueue<ITask> taskQueue;
- // 这个办事窗口是否在等待着办事。
- private boolean isRunning = true;
- public TaskExecutor(BlockingQueue<ITask> taskQueue) {
- this.taskQueue = taskQueue;
- }
- // 下班。
- public void quit() {
- isRunning = false;
- interrupt();
- }
- @Override
- public void run() {
- while (isRunning) { // 如果是上班状态就待着。
- ITask iTask;
- try {
- iTask = taskQueue.take(); // 叫下一个办事的人进来,没有人就等着。
- } catch (InterruptedException e) {
- if (!isRunning) {
- // 发生意外了,是下班状态的话就把窗口关闭。
- interrupt();
- break; // 如果执行到break,后面的代码就无效了。
- }
- // 发生意外了,不是下班状态,那么窗口继续等待。
- continue;
- }
- // 为这个办事的人办事。
- iTask.run();
- }
- }
- }
这里要稍微解释下BlockingQueue<T>#take()
方法,这个方法当队列里面的item
为空的时候,它会一直处于阻塞状态,当队列中进入item
的时候它会立刻有一个返回值,它就和ServerSocket.accept()
方法一样,所以我们把它放入一个Thread
中,以免阻塞调用它的线程(Android中可能是主线程)。
办事的人和窗口都有了,下面我们封装一个队列,也就是某机构,用来管理这些窗口:
- // 某机构。
- public class TaskQueue {
- // 某机构排的队,队里面是办事的人。
- private BlockingQueue<ITask> mTaskQueue;
- // 好多窗口。
- private TaskExecutor[] mTaskExecutors;
- // 在开发者new队列的时候,要指定窗口数量。
- public TaskQueue(int size) {
- mTaskQueue = new LinkedBlockingQueue<>();
- mTaskExecutors = new TaskExecutor[size];
- }
- // 开始上班。
- public void start() {
- stop();
- // 把各个窗口都打开,让窗口开始上班。
- for (int i = 0; i < mTaskExecutors.length; i++) {
- mTaskExecutors[i] = new TaskExecutor(mTaskQueue);
- mTaskExecutors[i].start();
- }
- }
- // 统一各个窗口下班。
- public void stop() {
- if (mTaskExecutors != null)
- for (TaskExecutor taskExecutor : mTaskExecutors) {
- if (taskExecutor != null) taskExecutor.quit();
- }
- }
- // 开一个门,让办事的人能进来。
- public <T extends ITask> int add(T task) {
- if (!mTaskQueue.contains(task)) {
- mTaskQueue.add(task);
- }
- // 返回排的队的人数,公开透明,让外面的人看的有多少人在等着办事。
- return mTaskQueue.size();
- }
- }
某机构、窗口、办事的人都有了,下面我们就派一个人去一件具体的事,但是上面我的Task
是一个接口,所以我们需要用一个类来实现这个接口,来做某一件事:
- // 做一件打印自己的id的事。
- public class PrintTask implements ITask {
- private int id;
- public PrintTask(int id) {
- this.id = id;
- }
- @Override
- public void run() {
- // 为了尽量模拟窗口办事的速度,我们这里停顿两秒。
- try {
- Thread.sleep(2000);
- } catch (InterruptedException ignored) {
- }
- System.out.println("我的id是:" + id);
- }
- }
下面就让我们模拟的虚拟世界运行一次:
- public class Main {
- public static void main(String... args) {
- // 这里暂时只开一个窗口。
- TaskQueue taskQueue = new TaskQueue(1);
- taskQueue.start();
- for (int i = 0; i < 10; i++) {
- PrintTask task = new PrintTask(i);
- taskQueue.add(task);
- }
- }
- }
没错,队列按照我们理想的状况打印出来了:
- 我的id是:0
- 我的id是:1
- 我的id是:2
- 我的id是:3
- 我的id是:4
- 我的id是:5
- 我的id是:6
- 我的id是:7
- 我的id是:8
- 我的id是:9
上面我门只开了一个窗口,下面我多开几个窗口:
- public class Main {
- public static void main(String... args) {
- // 开三个窗口。
- TaskQueue taskQueue = new TaskQueue(3);
- taskQueue.start(); // 某机构开始工作。
- for (int i = 0; i < 10; i++) {
- // new 10 个需要办事的人,并且进入某机构办事。
- PrintTask task = new PrintTask(i);
- taskQueue.add(task);
- }
- }
- }
这里要说明一下,在初始化的时候我们开了3个窗口,内部的顺序应该是这样的:
当某机构的大门开了以后,第一个办事的人进去到了第一个窗口,第二个办事的人进去到了第二个窗口,第三个办事的人进去到了第三个窗口,第四个办事的人进去排队在第一位,当第一、第二、第三个窗口中不论哪一个窗口的事办完了,第四个人就去哪一个窗口继续办事,第五个人等待,一次类推。这样子就达到了队列同事并发三个任务的效果。
这就是一个普通的队列,其它的一些特性也是基于此再次封装的,那么下面我就基于此再把人物的优先级加上,也就是我们上面说的特殊窗口->军人优先!
优先级队列
我们排队等待办事的时候,来了一个办事的人,那么如何判断这个办事人是否可以优先办理呢?那就要判断它是否具有优先的权限甚至他可以优先到什么程度。
所以我们需要让这个Task
有一标志,那就是优先级,所以我用一个枚举类标记优先级:
- public enum Priority {
- LOW, // 最低。
- DEFAULT, // 默认级别。
- HIGH, // 高于默认级别。
- Immediately // 立刻执行。
- }
这里我把分了四个等级:最低
、默认
、高
、立刻
,这个等级肯定要给到我们的办事的人,也就是Task
- public interface ITask {
- void run();
- void setPriority(Priority priority);
- Priority getPriority();
- }
可以设置优先级和可以拿到优先级。
下面我们要把上面的LinkedBlockingQueue
替换成PriorityBlockingQueue<E>
,因为它可以自动做到优先级的比较,它要求泛型<E>
,也就是我们的Task
必须实现Comparable<E>
接口,而Comparable<E>
有一个compareTo(E)
方法可以对两个<T>
做比较,因此我们的队列需要改一下实现的方法:
- // 某机构。
- public class TaskQueue {
- // 某机构排的队,队里面是办事的人。
- private BlockingQueue<ITask> mTaskQueue;
- // 好多窗口。
- private TaskExecutor[] mTaskExecutors;
- // 在开发者new队列的时候,要指定窗口数量。
- public TaskQueue(int size) {
- mTaskQueue = new PriorityBlockingQueue<>();
- mTaskExecutors = new TaskExecutor[size];
- }
- ...
然后ITask
接口继承Comparable<E>
接口:
- public interface ITask extends Comparable<ITask> {
- void run();
- void setPriority(Priority priority);
- Priority getPriority();
- }
因为有setPriority(Priority)
方法和getPriority()
方法和Comparable<E>
的compareTo(E)
方法,所以我们的每一个Task
都需要实现这几个方法,这样就会很麻烦,所以我们封装一个BasicTask
:
- public abstract class BasicTask implements ITask {
- // 默认优先级。
- private Priority priority = Priority.DEFAULT;
- @Override
- public void setPriority(Priority priority) {
- this.priority = priority;
- }
- @Override
- public Priority getPriority() {
- return priority;
- }
- // 做优先级比较。
- @Override
- public int compareTo(ITask another) {
- final Priority me = this.getPriority();
- final Priority it = another.getPriority();
- return me == it ? [...] : it.ordinal() - me.ordinal();
- }
- }
其它都好说,我们看到compareTo(E)
方法就不太理解了,这里说一下这个方法:
compareTo(E)
中传进来的E是另一个Task
,如果当前Task
比另一个Task
更靠前就返回负数,如果比另一个Task
靠后,那就返回正数,如果优先级相等,那就返回0。
这里要特别注意,我们看到上面当两个Task
优先级不一样的时候调用了Priority.orinal()
方法,并有后面的orinal
减去了当前的orinal
,怎么理解呢?首先要理解Priority.orinal()
方法,在Java中每一个枚举值都有这个方法,这个枚举的值是它的下标+1,也就是[index + 1]
,所以我们写的Priority
类其实可以这样理解:
- public enum Priority {
- 1,
- 2,
- 3,
- 4
- }
继续,如果给当前Task
比较低,给compareTo(E)
中的Task
设置的优先级别比较高,那么Priority
不一样,那么返回的值就是整数,因此当前Task
就会被PriorityBlockingQueue<E>
排到后面,如果调换那么返回结果也就调换了。
但是我们注意到me == it ? [...] : it.ordinal() - me.ordinal();
中的[...]
是什么鬼啊?因为这里缺一段代码呀哈哈哈(这个作者怎么傻乎乎的),这一段代码的意思是当优先级别一样的时候怎么办,那就是谁先加入队列谁排到前面呗,那么怎样返回值呢,我们怎么知道哪个Task
先加入队列呢?这个时候可爱的我就出现了,我给它给一个序列标记它什么时候加入队列的不久完事了,于是我们可以修改下ITask
接口,增加两个方法:
- public interface ITask extends Comparable<ITask> {
- void run();
- void setPriority(Priority priority);
- Priority getPriority();
- void setSequence(int sequence);
- int getSequence();
- }
我们用setSequence(int)
标记它加入队列的顺序,然后因为setSequence(int)
和getSequence()
是所有Task
都需要实现的,所以我们在BasicTask
中实现这两个方法:
- public abstract class BasicTask implements ITask {
- // 默认优先级。
- private Priority priority = Priority.DEFAULT;
- private int sequence;
- @Override
- public void setPriority(Priority priority) {
- this.priority = priority;
- }
- @Override
- public Priority getPriority() {
- return priority;
- }
- @Override
- public void setSequence(int sequence) {
- this.sequence = sequence;
- }
- @Override
- public int getSequence() {
- return sequence;
- }
- // 做优先级比较。
- @Override
- public int compareTo(ITask another) {
- final Priority me = this.getPriority();
- final Priority it = another.getPriority();
- return me == it ? this.getSequence() - another.getSequence() :
- it.ordinal() - me.ordinal();
- }
- }
看到了吧,刚才的[...]
已经变成了this.getSequence() - another.getSequence()
,这里需要和上面的it.ordinal() - me.ordinal();
的逻辑对应,上面说到如果给当前Task
比较低,给compareTo(E)
中的Task
设置的优先级别比较高,那么Priority
不一样,那么返回的值就是整数,因此当前Task
就会被PriorityBlockingQueue<E>
排到后面,如果调换那么返回结果也就调换了。
这里的逻辑和上面对应就是和上面的逻辑相反,因为这里是当两个优先级一样时的返回,上面是两个优先级不一样时的返回,所以当优先级别一样时,返回负数表示当前Task
在前,返回正数表示当前Task
在后,正好上面上的逻辑对应。
接下来就是给Task
设置序列了,于是我们在TaskQueue
中的T void add(T)
方法做个手脚:
- public class TaskQueue {
- private AtomicInteger mAtomicInteger = new AtomicInteger();
- ...
- public TaskQueue(int size) {
- ...
- }
- public void start() {
- ...
- }
- public void stop() {
- ...
- }
- public <T extends ITask> int add(T task) {
- if (!mTaskQueue.contains(task)) {
- task.setSequence(mAtomicInteger.incrementAndGet()); // 注意这行。
- mTaskQueue.add(task);
- }
- return mTaskQueue.size();
- }
- }
这里我们使用了AtomicInteger
类,它的incrementAndGet()
方法会每次递增1,其实它相当于:
- mAtomicInteger.addAndGet(1);
其它具体用法请自行搜索,这里不再赘述。
到此为止,我们的优先级别的队列就实现完毕了,我们来做下测试:
- public static void main(String... args) {
- // 开一个窗口,这样会让优先级更加明显。
- TaskQueue taskQueue = new TaskQueue(1);
- taskQueue.start(); // // 某机构开始工作。
- // 为了显示出优先级效果,我们预添加3个在前面堵着,让后面的优先级效果更明显。
- taskQueue.add(new PrintTask(110));
- taskQueue.add(new PrintTask(112));
- taskQueue.add(new PrintTask(122));
- for (int i = 0; i < 10; i++) { // 从第0个人开始。
- PrintTask task = new PrintTask(i);
- if (1 == i) {
- task.setPriority(Priority.LOW); // 让第2个进入的人最后办事。
- } else if (8 == i) {
- task.setPriority(Priority.HIGH); // 让第9个进入的人第二个办事。
- } else if (9 == i) {
- task.setPriority(Priority.Immediately); // 让第10个进入的人第一个办事。
- }
- // ... 其它进入的人,按照进入顺序办事。
- taskQueue.add(task);
- }
没错这就是我们看到的效果:
- 我的id是:9
- 我的id是:8
- 我的id是:110
- 我的id是:112
- 我的id是:122
- 我的id是:0
- 我的id是:2
- 我的id是:3
- 我的id是:4
- 我的id是:5
- 我的id是:6
- 我的id是:7
- 我的id是:1
Java的优先级任务队列的实践的更多相关文章
- [转载]Java 应用性能调优实践
Java 应用性能调优实践 Java 应用性能优化是一个老生常谈的话题,笔者根据个人经验,将 Java 性能优化分为 4 个层级:应用层.数据库层.框架层.JVM 层.通过介绍 Java 性能诊断工具 ...
- paip.java gui swt/jface 最佳实践
paip.java gui swt/jface 最佳实践 1. 工具:Eclipse +jigloo4 1 2. 安装插件: 1 1. IMPORT swt lib 2 2. 新建立窗体 2 3. 运 ...
- Java运算符优先级(转)
转自:http://www.cnblogs.com/gw811/archive/2012/10/13/2722752.html Java运算符优先级 序列号 符号 名称 结合性(与操作数) 目数 说明 ...
- 【转】Java运算符优先级
原文网址:http://www.cnblogs.com/gw811/archive/2012/10/13/2722752.html Java运算符优先级 序列号 符号 名称 结合性(与操作数) 目数 ...
- java多线程中最佳的实践方案是什么?
java多线程中最佳的实践方案是什么? 给你的线程起个有意义的名字.这样可以方便找bug或追踪.OrderProcessor, QuoteProcessor or TradeProcessor 这种名 ...
- Java序列化与反序列化(实践)
Java序列化与反序列化(实践) 基本概念:序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数据. 昨天在一本书上 ...
- paip.提升性能--多核编程中的java .net php c++最佳实践 v2.0 cah
paip.提升性能--多核编程中的java .net php c++最佳实践 v2.0 cah 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax ...
- JAVA异常的最佳工程学实践探索
此文已由作者占金武授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 先说明一下背景: 项目日志中的Exception会被哨兵统一监控并报警 比较多的项目基于dubbo在做服务化 ...
- atitit.Atitit. Gui控件and面板-----服务端控件 java struts的实现最佳实践
atitit.Atitit. Gui控件and面板-----服务端控件 java struts的实现最佳实践 1. 服务器控件的类别 1 1.1. 数据控件:该类控件可细分为两种类型:数据源控件和数 ...
随机推荐
- JS日期相减得到天数
废话不多说直奔主题,解决思路核心是时间戳相减 灵感来自于我经常用到一个工具方法:格式化时间,也是用时间戳 function GetNumberOfDays(date1,date2){//获得天数 / ...
- 解决SVN不显示绿色图标问题
今天是上班的第50天,发现项目上的svn绿色图标没有了,于是上网查了一下,然后很简单的就找到了解决办法: 修改注册表 Windows Explorer Shell支持Overlay Icon最多15个 ...
- Java多线程之一
进程与线程 进程 进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位,比如我们windows电脑上运行的一个程序就是一个进程.在传统进程中进程是资源分配和调度的一个基本单位,在后来引入线 ...
- idea启动多个tomcat失败
Intellij idea中,为在本地调试两个系统之间的调用,配置两个本地tomcat server,设置不同的端口号,如8081和8082,Deploy中加入两个系统各自的Artifact xxx: ...
- MySQL 通讯协议
Client/Server 通讯协议用于客户端链接.代理.主备复制等,支持 SSL.压缩,在链接阶段进行认证,在执行命令时可以支持 Prepared Statements 以及 Stored Proc ...
- Deploying Keras model on Tensorflow Serving--
keras训练了个二分类的模型.需求是把keras模型跑到 tensorflow serving上 (TensorFlow Serving 系统用于在生产环境中运行模型) keras模型转 tenso ...
- Go基础系列:channel入门
Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 channel基础 chann ...
- python下载安装requests库
一.python下载安装requests库 1.到git下载源码zip源码https://github.com/requests/requests 2.解压到python目录下: 3.“win+R”进 ...
- C#获取某一路径下的所有文件名信息(包括子文件夹)
前言:初步梳理记录,以后慢慢总结更多的方法... 方法一:使用微软提供的方法:Dicrectory类中的:public static string[] GetFiles(string path, st ...
- [转]windows 10 搭建angular开发环境
本文转自:https://www.cnblogs.com/lilunpai/articles/7992538.html 一.环境介绍 1.开发环境:Windows10 2.开发ide工具:VS cod ...