异步处理的深入研究 二

Thread

Executor

HandlerThread

AsyncTask

Service

IntentService

AsyncQueryHandler

Loader


Executor

Executor只是一个接口,而在使用它的时候实际上是多个类共同作用的,且有其自己的使用套路,相关的类太多了,如果挨着挨着来介绍估计连自己都乱了,如果直接介绍其主要几个类根本就无法理解它的原理所在,所以这次就用一个开源框架为引子来揭示它的用法以及它的原理,依然不要忘记,多线程玩过去玩过来,玩的都是Thread对象,这点一定要清晰。

Picasso是一个图片加载框架,我们从其中一个类PicassoExecutorService看起。


class PicassoExecutorService extends ThreadPoolExecutor {
  private static final int DEFAULT_THREAD_COUNT = 3;

  PicassoExecutorService() {
    super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
        new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
  }

  void adjustThreadCount(NetworkInfo info) {
    if (info == null || !info.isConnectedOrConnecting()) {
      setThreadCount(DEFAULT_THREAD_COUNT);
      return;
    }
    switch (info.getType()) {
      case ConnectivityManager.TYPE_WIFI:
      case ConnectivityManager.TYPE_WIMAX:
      case ConnectivityManager.TYPE_ETHERNET:
        setThreadCount(4);
        break;
      case ConnectivityManager.TYPE_MOBILE:
        switch (info.getSubtype()) {
          case TelephonyManager.NETWORK_TYPE_LTE:  // 4G
          case TelephonyManager.NETWORK_TYPE_HSPAP:
          case TelephonyManager.NETWORK_TYPE_EHRPD:
            setThreadCount(3);
            break;
          case TelephonyManager.NETWORK_TYPE_UMTS: // 3G
          case TelephonyManager.NETWORK_TYPE_CDMA:
          case TelephonyManager.NETWORK_TYPE_EVDO_0:
          case TelephonyManager.NETWORK_TYPE_EVDO_A:
          case TelephonyManager.NETWORK_TYPE_EVDO_B:
            setThreadCount(2);
            break;
          case TelephonyManager.NETWORK_TYPE_GPRS: // 2G
          case TelephonyManager.NETWORK_TYPE_EDGE:
            setThreadCount(1);
            break;
          default:
            setThreadCount(DEFAULT_THREAD_COUNT);
        }
        break;
      default:
        setThreadCount(DEFAULT_THREAD_COUNT);
    }
  }

  private void setThreadCount(int threadCount) {
    setCorePoolSize(threadCount);
    setMaximumPoolSize(threadCount);
  }

  @Override
  public Future<?> submit(Runnable task) {
    PicassoFutureTask ftask = new PicassoFutureTask((BitmapHunter) task);
    execute(ftask);
    return ftask;
  }

  private static final class PicassoFutureTask extends FutureTask<BitmapHunter>
      implements Comparable<PicassoFutureTask> {
    private final BitmapHunter hunter;

    public PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }

    @Override
    public int compareTo(PicassoFutureTask other) {
      Picasso.Priority p1 = hunter.getPriority();
      Picasso.Priority p2 = other.hunter.getPriority();

      // High-priority requests are "lesser" so they are sorted to the front.
      // Equal priorities are sorted by sequence number to provide FIFO ordering.
      return (p1 == p2 ? hunter.sequence - other.hunter.sequence : p2.ordinal() - p1.ordinal());
    }
  }
}

该类的代码不多,先说adjustThreadCount方法,它做的事情就是根据网络类型来调用setThreadCount设置线程数量,setThreadCount方法里调用的是其父类的两个方法,一个是setCorePoolSize,一个是setMaximumPoolSize,这两个都是用于动态设置线程数量阀值的,而初始阀值是通过构造方法设置的DEFAULT_THREAD_COUNT。而理解corePoolSize和maximumPoolSize两个阀值是很重要的,上面这个类 PicassoExecutorService继承了ThreadPoolExecutor, ThreadPoolExecutor类内部就定义了上面两个阀值,以及所谓的线程池HashSet<Worker> workers和所谓的排队队列BlockingQueue<Runnable> workQueue,从它的构造函数可以明白的看到:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
需要注意的是线程池是一个动态的集合,所以我们才可以动态设置线程池的大小,添加操作是没有限制的,而真正限制线程数的规则是围绕两个阀值与线程池的大小进行比对展开的。回到 PicassoExecutorService,里面的执行任务的方法被调用了,其父类是这样处理的:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

需要提出的是 PicassoExecutorService传入父类的排队队列是new PriorityBlockingQueue<Runnable>(),所以其大小是一个默认值为11.那么根据PicassoExecutorService的实际情况,它添加一个新任务的流程如下:

1 如果线程池的当前大小还没有达到corePoolSize,那么就新增加一个线程处理新提交的任务;

2 如果线程池的当前大小已经达到了corePoolSize,就将新提交的任务提交到workQueue排队;

3 如果workQueue已满,并且当前大小没有达到maximumPoolSize,那么就新增线程来处理任务;

4 如果workQueue已满,并且当前大小也已经达到maximumPoolSize,那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。


还是以车库为例子,但变化不小。关于车库的实际大小,我们可以变相理解为maximumPoolSize,那么假设maximumPoolSize=12,也就是说这个车库从物理面积来说最多只能摆下12个移动车位即线程(之所以称之为移动车位是指该车位可以立即从库房取来安装好直接使用),而corePoolSize=10,就是说我们建议车库里摆10个就差不多了,否则车位之间会变得很狭窄拥挤,用户体验不好,最后假设入口处排队区最多只能排10辆车那么根据上面的流程来看,车子停车就是一个task,最多可以排10辆车也就是说workQueue的容量为10,准备就绪流程如下

1 车库开门,过了一会儿,第一辆车开进门,发现里面只有一个安装好的移动车位,它正准备停进去,却被工作人员拦下来,工作人员立马从库房里面拿出了第二个移动车位安装好,让它停进去。

2 第十辆车进来了之后发现车库里先前停的车全部已经离开了,剩下了九个空移动车位,却还是被工作人员拦下,拿出第十个移动车位按好让其停了进去。

3 第十一辆车来了,它先开到排队区,得到通知说里面有空车位,然后它离开排队区开进了车库找好车位停了。

4 就这样连续来了很多车停好后,车库里面的车位都满了,这时又来了一辆车得知车位已满,于是只能在排队区等候。

5 当排队区的已经有10辆车在等候的时候,又来了两辆车,情急之下,车库老板下令再拿出两个移动车位放在车库的最后两块面积上,排队区的头两辆车开进去了,这两辆车进入了排队区等候。

6 最后一辆车开来了,发现车库满了,排队区也满了,找老板理论,老板摆摆手车库里已经没有多余的地方了,于是这辆车没办法只得离开了。

以上就是需要掌握的基本知识,而实际情况没有这么简单,比如说获取当前还剩几个空车位,以及排队的队列中如果有车非常着急需要先停车等等。需要注意的几个点如下:

1 为什么把车位比作thread,把停车看作task。这里需要明白一个概念,idle-thread 空闲线程,当一辆车进车库找到车位,熄火停车这四个字就意味着这个task正在执行,执行的事情就是车停在车位上。那么空闲线程就是这辆车在这个车位上停了两分钟开走之后,下一辆车停进来之前,这个车位就处于空闲状态。那么明白了空闲线程,就可以很好理解线程的复用了,复用的当然就是这些空闲线程了,后面会讲到复用过程。反过来说,一个线程是不能重复start的,执行完了就死了,何谈复用呢,所以一个task执行完了就以为该thread即将进入death是不对的,而是sleep了,当发现有新任务交给它时,那么它就会被唤醒执行这个任务,接着又sleep了。

2 阻塞队列。上面提到的空闲线程的产生实际上是人为的,为了保证线程池里的基本线程数,即HashSet<Worker> workers的大小不能太多也不能太少,因此该类为在每个添加进该线程池的线程作了一层包装,它的名字就叫Worker,其代码如下:


private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

包装的目的有很多,多数是与线程安全相关的东西,我们这里只看run方法,里面调用的是runWorker方法,该方法在ThreadPoolExecutor中,代码如下:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }</span>

上面getTask()是从排队区获取task的意思,而该方法存在阻塞线程的语句,另一个task.run()是执行任务的方法。也就是说,一旦我们让worker启动自己的线程,run方法就会调用runworker方法,一旦进入就会去排队区获取task,需要注意的是getTask是放在一个循环语句中的,当这一个task执行完毕之后会再次从排队区获取task来执行,这就是线程的复用了。那么继续看getTask方法:


private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

可以看到,for (;;)是无限循环,另一个workQueue.take()是take方法。需要介绍的是BlockQueue,即阻塞队列,它有一个特性,当它为空时从里面取的操作会产生线程阻塞,当它为满时往里面加的操作也会产生线程阻塞,而添加的方法叫put,取走的方法就是take了,那么当排队区为空时,每个线程其实都阻塞在这个gettask方法里的,一旦排队区有新task进来,马上就会被捕获,线程就会被唤醒来执行这个task了,执行完毕之后就又被阻塞了,空闲线程也就由此产生。那么回到PicassoExecutorService类,它传递给父类构造方法时传递的是PriorityBlockingQueue,即BlockQueue的实现类之一,其余三个分别是ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue,PriorityBlockingQueue与这三个最大的区别就是它可以根据任务的优先级来排队而不是先进先出。那么PicassoExecutorService类底部的代码就是在compareTo方法里来根据优先级为task排序,优先级高的排在前面低的排在后面。


最后需要介绍PicassoExecutorService最后一个点,PicassoFutureTask,它继承自FutureTask,先来看构造函数及其父类的构造函数:

public PicassoFutureTask(BitmapHunter hunter) {
      super(hunter, null);
      this.hunter = hunter;
    }

BitmapHunter是一个实现了Runnable的类,其run方法实际就是在请求图片数据并获得结果。

public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result>);
        this.state = NEW;       // ensure visibility of callable
    }

通过代码来看,实际上就是给FutureTask的private Callable<V> callable实例化了。而通过调用其call方法再调用RunnableAdapter的call最终执行的 BitmapHunter的run方法,似乎绕了一大圈,原因是上面红色标记的result传的是null,不太明白为何要大费周章的这样做,这里先不管。关于Callable、Future 以及 FutureTask,需要有一个基本了解,这里结合Runnable来理解就好解释多了,Callable和Runnable的唯一区别就是它的call有返回值,Future可以取得该返回值,因为它的get方法会产生线程阻塞和上面阻塞队列的take一样的效果。而FutureTask本身实现了Future接口说明有获取返回值的能力,又持有Callable对象,说明它也有执行task并返回值的能力。


以上就对PicassoExecutorService这个自定义的线程池服务类的分析了,实际上是通过它来窥探ThreadPoolExecutor的部分机制,而并没有提及像Executors这样的实际项目中常会用到的静态工厂类,主要是因为Executors提供的一些方法都是依靠的ThreadPoolExecutor。

你可能感兴趣的:(异步处理的深入研究 二)