此篇文章将从任务队列的设计;任务调度的方式(串行和并行)。代码很简单,主要是设计的思想。
任务队列
final class PendingPostQueue {
// 含有头、尾指针的链表结构实现队列
private PendingPost head;
private PendingPost tail;
// 入队列
synchronized void enqueue(PendingPost pendingPost) {
if (pendingPost == null) {
throw new NullPointerException("null cannot be enqueued");
}
if (tail != null) {
tail.next = pendingPost;
tail = pendingPost;
} else if (head == null) {
head = tail = pendingPost;
} else {
throw new IllegalStateException("Head present, but no tail");
}
notifyAll();
}
// 出队列
synchronized PendingPost poll() {
PendingPost pendingPost = head;
if (head != null) {
head = head.next;
if (head == null) {
tail = null;
}
}
return pendingPost;
}
// 等待最大时长; 如果此时有入队列的操作(notifyAll),直接出队列
synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
if (head == null) {
wait(maxMillisToWait);
}
return poll();
}
}
上面的代码很简单,基本上一看就能明白;下面主要分析,这样设计的优点:
使用头、尾指针的链表结构实现队列;入队列通过操作尾指针,出队列通过操作头指针的方式达到时间复杂度都是O(1).
增加出队列延迟的功能,方式在空队列的时候,持续获取或直接返回空;增加一段时间间隔等待其他线程的入队列的操作(尽可能处理尽量多的任务。)
任务调度:串行执行
串行的任务调度,基本上是单线程模型。因为基本上是下一个任务的执行需要等到上一个任务执行完成。
代码如下:
// 当前任务调度类(串行)
final class BackgroundPoster implements Runnable {
// 任务队列
private final PendingPostQueue queue;
// 当前线程是否在正在运行
// volatile: 保证单个变量的读写操作是线程安全(通过cpu实现CAS)
private volatile boolean executorRunning;
BackgroundPoster() {
queue = new PendingPostQueue();
}
public void enqueue(String id, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(id, event); // 创建任务
synchronized (this) {
queue.enqueue(pendingPost); // 入队列
// 如果当前没有正在运行的任务,开启任务
if (!executorRunning) {
executorRunning = true;
ThreadUtils.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
while (true) {
// 从任务队列中获取任务;设置一分钟时间间隔,防止在1000分钟内有新任务入队列
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// 双层检验
pendingPost = queue.poll();
if (pendingPost == null) {
// 运行标志置为false
executorRunning = false;
return; // 如果没有任务了,将会结束此次循环,也就相当于停止了当前线程(也正因为此,上面的wait(1000)才很重要)
}
}
}
// 执行任务
invokePost(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
上面的代码也不难,对照我写的注释看起来会很简单。原理也很简单:
任务的执行是在一个子线程(通过线程池开启的)中
任务的调度是通过操作任务队列实现的,通过循环依次调用队列中的任务。
wait(1000)的作用,最大化使用线程资源;防止队列中刚没有任务了就停止线程(具体分析在注释中)
任务调度:并行执行
并行调度任务,就需要多线程调度了。
具体代码实现如下:
class AsyncPoster implements Runnable {
private final PendingPostQueue queue;
AsyncPoster() {
queue = new PendingPostQueue();
}
public void enqueue(String id, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(id, event);
queue.enqueue(pendingPost);
ThreadUtils.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
invokePost(pendingPost);
}
}
上面的代码更简单,就是每一个任务开启一个线程去执行。
但是如果仔细查看代码会发现:
这里根本就没有必要使用任务队列,直接开启线程去执行任务不就行了吗?这里任务队列的作用是用来传递数据。
任务调度:Android主线程调度
我们经常会遇到:回调在主线程中执行。由于主线程只有一个,也就相当于上面的串行执行。而Android有自己的Handler消息机制帮我们封装好了,下面就基于这个来实现。
final class HandlerPoster extends Handler {
private final PendingPostQueue queue;
// 主线程执行最大时长(防止阻塞主线程)
private final int maxMillisInsideHandleMessage;
// 正在运行的标志(同串行执行)
private boolean handlerActive;
// 参数looper决定了当前任务所运行的线程,这里传递Looper.mainLooper()就会将当前任务运行在主线程中
HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}
void enqueue(String id, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(id, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
// 发送消息
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
invokePost(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
// 如果在主线程中执行的时间超过最大时间,停止当前操作,重新发送消息;防止祖册主线程
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
//重置执行,也就是还处于运行状态。
rescheduled = true;
return;
}
}
} finally {
// 运行状态由rescheduled决定
handlerActive = rescheduled;
}
}
}
代码也不难,原理基本和串行调度相同;唯一不同,因为是在主线程中,需要对线程阻塞的问题进行考虑。
---------------------
作者:qiaoba_gogo
来源:CSDN
原文:https://blog.csdn.net/u010014658/article/details/77925567