转载

深入OKHttp源码分析(二)----OkHttp任务调度核心类Dispatcher解析

上一篇我们分析了okhttp的同步和异步请求的执行流程并进行了源码分析, 深入OKHttp源码分析(一)----同步和异步请求流程和源码分析 那么今天我们来看看在整个执行流程中起到关键作用的Dispatcher调度类。首先我们来看看这个类中的几个全局变量

/** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

我们先来看下面三个变量 readyAsyncCalls:等待执行的异步任务队列,用来存放异步任务,如果线程池中的任务数量已经超过或已经达到指定的最大的任务数,那么久将异步任务放入这个队列中。 runningAsyncCalls:正在执行的异步任务队列,如果线程池中的任务数量小于指定的最大任务数,就将异步任务添加到这个队列中, runningSyncCalls:正在执行的同步任务队列

接着回来我们看executorService,这个是一个线程池,怎么证明呢,我们可以看到下面这个方法

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

我们可以看到,果然是new出来一个线程池并复制给了executorService,我们来看下线程池的构造函数中的参数,来一一分析下: 第一个参数0,指的是这个线程池中的核心线程数量,如果线程池中没有任务的话,那么非核心线程在经过一段时间的等待后就会关闭,以节省资源。 第二个参数Integer.MAX_VALUE,这个参数指的是这个线程池中的最大线程数,设置成了integer的最大值,我们知道,这个值是非常大的,如果真的创建了这么多的线程,那么我们的手机不会挂掉吗???其实不会的,因为okhttp在其他地方已经进行了判断,如果线程池中的线程数量达到了指定的数值,就不会将任务添加到线程池中,也就不会新建线程了。具体是在哪里判断的,我们稍后介绍。 第三个参数60和第四个参数TimeUnit.SECONDS,这个指的就是空闲线程的存活时间,第三个参数指定数值,第四个参数指定单位。 第五个参数new SynchronousQueue():其实就是线程池中的线程的管理队列 第六个参数Util.threadFactory("OkHttp Dispatcher", false):这个是线程的创建工厂,线程的创建就是通过第六个参数进行的,有兴趣的朋友可点进去继续看下,我们就不贴出来啦。

我们再来看下上一篇遇到过的一个方法

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

我们看下if语句的条件中的两个变量maxRequests,maxRequestsPerHost,我们看下定义,

private int maxRequests = 64;
  private int maxRequestsPerHost = 5;

这两个值是什么意思呢,其实,maxRequests就是线程池中的最大任务数,当线程池中的任务数量达到这个值之后,就不再往线程池中继续添加任务,所以,即使在创建线程池的时候指定了线程池的最大任务数是integer的最大值,也是不可能达到的,这就是原因。maxRequestsPerHost是每个主机的最大请求数,当线程池中的任务对同一个主机的请求达到这个数值之后,就不能继续往线程池中添加对这个主机的请求任务,不知道这样说大家能不能理解???? 我们回过头来看下这条语句runningCallsForHost(call),点进去看下这个方法的实现

private int runningCallsForHost(AsyncCall call) {
    int result = 0;
    for (AsyncCall c : runningAsyncCalls) {
      if (c.get().forWebSocket) continue;
      if (c.host().equals(call.host())) result++;
    }
    return result;
  }

我们可以看到,里面是对线程池中正在执行的任务进行遍历操作,当要添加的任务的请求的主机和正在进行的任务的主机一致,result就加1,最后得到对这个主机的请求数量,然后在前面的if语句中进行判断是否小于指定的值,如果小于,就将异步任务添加到正在执行的异步任务队列中,并添加到线程池中,否则就添加到等待的异步任务队列中。

我们再来看一个前面分析过的方法,finished方法,这个方法在哪里会被调用呢?如果你不清楚,请看前面一篇文章的分析,我们看下这个方法的实现

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

在加锁的代码块中,首先将这个异步请求的任务从执行的队列中移除,然后我们看下这句

if (promoteCalls) promoteCalls();

我们看下实现

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

首先判断当前正在运行的队列中的任务数量是否达到了指定的数值,如果等于或大于指定数值,就返回,接着判断等待队列是否为空,如果为空,说明没有要添加的任务,也返回即可。 下面对等待的任务队列进行遍历,首先取出一个异步任务,判断该任务请求的主机地址在正在执行的队列中的数量是否达到了指定的最大任务数,如果没有达到,就从等待队列中移除,并添加到正在运行的队列中,并加入线程池中执行。 最后判断正在运行的任务队列中的任务数量是否达到了指定的最大任务数,如果大于最大任务数,就跳出for循环,否则,就继续从等待队列中取出任务进行操作。 这个方法分析完了,我们回到finished方法中,继续往下看

runningCallsCount = runningCallsCount();

上一篇我们介绍过,这个语句只是重新计算了正在执行的任务数量,包括同步和异步任务。 finished方法最后部分是判断运行任务数量是否为0,如果没有了运行任务,就执行回收线程进行资源回收操作。 到此为止呢,我们就将okhttp的任务调度核心类Dispatcher的主要代码又分析了一遍,相信大家能对这个类有了较为深刻的认识了,这个类的主要功能就是对同步和异步任务进行调度处理,相当于任务的指挥者,大概就像是十字路口的交警同志吧,让你等你就等,让你走你就得走,可能比喻不是非常恰当,只要大家都明白即可,下一篇我们就对okhttp的拦截器进行介绍和分析。

原文  https://juejin.im/post/5acf408ef265da239c7bbfa5
正文到此结束
Loading...