java多线程任务队列模型

此篇文章将从任务队列的设计;任务调度的方式(串行和并行)。代码很简单,主要是设计的思想。

任务队列
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 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值