Volley学习之Java多线程分发器实例

April 29, 2016

前言

Volley 是个优秀的网络请求框架,它的设计决定了它适合多个并行的小规模的请求,比如大量的接口请求。今天我们把Volley身上所有的皮肉都剃掉,只保留一个骨架,这个骨架对于我们理解如何实现一个多线程分发器很有帮助,首先我们先来看看Volley的架构图:

Volley

从使用者这一侧,我们只需要一个RequestQueue来放入请求,一旦请求放入了队列,首先会判断是否需要缓存,如果说这个请求明确不要缓存,就直接进入NetworkQueue,如果没有明确不缓存,则进入CacheQueue(换句话说就是,非特殊指定的请求默认都会缓存)。CacheDispatcher拿到Request后进入Cache检查,如果未命中Cache,则CacheDispatcher会把这个Request移到NetworkQueue中。NetworkDispatcher则从NetworkQueue中取出Request,然后进行网络请求。两种Dispatcher通过ResponseDelivery将结果传递回Request的回调(Listener)中。

本文尝试把Volley的Dispatcher机制简化,以此学习一个通用的分发器,这是一个典型的生产者消费者问题,生产者和消费者不直接通信,而是通过阻塞队列的中间人,达到生产者和消费者之间解耦。对应上面的图,我们这里介绍的内容就是图中虚线框中的部分。

1.首先我们需要一个队列:

public class Queue {

    // 这个阻塞队列是核心,这里给队列加上了优先级
    private final PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<Task>(); 
    private DispatchThread[] mDispatchers;

    public Queue(int size) {
        // size就是我们消费者的数量,这里固定为size个,有些系统里可以动态增减
        mDispatchers = new DispatchThread[size]; 
    }

    public void add(Task task) {
        synchronized (queue) {
            queue.add(task);
        }
    }

    public void start() {
        stop();

        for (int i = 0; i < mDispatchers.length; i++) {
            // 将队列传给Worker线程
            DispatchThread thread = new DispatchThread(queue,i); 
            mDispatchers[i] = thread;
            thread.start();
        }

    }

    public void stop() {

    }
}

这段代码中的queue是我们整个系统的核心部分,所有的其它组件都是围绕他来运作的,线程的阻塞也是通过这个queue里的阻塞特性来实现的。

2. 然后是我们的Worker线程:

public class DispatchThread extends Thread {

    private BlockingQueue<Task> mQueue;
    private int mId;

    public DispatchThread(BlockingQueue<Task> queue, int id) {
        this.mQueue = queue; // 持有队列来获取数据
        mId = id;
    }

    @Override
    public void run() {
        while(true) {

        try {
            // 假如队列为空,则这个线程就把队列阻塞了,其它线程都需要等待。
            Task task = mQueue.take(); 
            System.out.print("Hi, Im thread:" + mId);
            task.hello();
            sleep(500);
            System.out.println("");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        }

    }
}

3. 最后就是我们的任务啦:

public class Task implements Comparable{
    private int mId;
    public Task(int i) {
        mId = i;
    }
    public void hello(){
        System.out.println("Hello Im task:" +mId);
    }

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

Task 比较简单,可以根据业务改变。

4. 来我们测试一下:

public class Main {

    public static void main(String[] args) {
        Queue queue = new Queue(4);

        for (int i = 0; i < 20; i ++) {
            queue.add(new Task(i));
        }

        queue.start();

        Scanner s = new Scanner(System.in);
        int a;
        while ((a = s.nextInt()) != -1) {
            queue.add(new Task(a));
        }
        System.exit(0);
    }

}

运行结果:

Hello Im task:0
Hello Im task:19
Hello Im task:18
Hello Im task:17
Hello Im task:16
Hello Im task:15
Hello Im task:14
Hello Im task:13
Hello Im task:12
Hello Im task:11
Hello Im task:10
Hello Im task:9
Hello Im task:8
Hello Im task:7
Hello Im task:5
Hello Im task:6
Hello Im task:4
Hello Im task:3
Hello Im task:1
Hello Im task:2

5. 总结

从上面的简化的例子我们可以学习到一个多线程并发的模式,这个模式可以解决大多数并发问题。而通过阻塞而不是轮询,可以降低CPU的消耗。