OkHttp内部分析

2019-01-15  本文已影响0人  jj_huang

OkHttp阻塞式同步请求

在realCall中需要注意:
1.一个call只能被请求一次。如果被请求过了,那么再次请求就会抛出异常。
2.okHttp内部调度器会将同步请求添加到一个队列当中。
3.okHttp会通过拦截器链来做实际的网络请求工作。

OkHttp非阻塞式异步请求

OkHttp内部线程池创建

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;
}

这个线程池是在Dispatcher中创建的。第一个参数“0”指的是核心线程数的数量,为0表示线程空闲后不会被保留。第二个参数表示线程池中可以容纳的最大线程数量。第三个是keep-alive-time,就说当我们线程池中线程数量大于我们的核心线程数时,他会等待60秒后才会被终止。第四个是一个创建好的等待线程队列,他也是个同步队列,先来先服务。最后一个是一个线程工厂,他会创建一个守护线程用来处理我们的Dispatcher类。需要注意的是 SynchronousQueue 这个线程等待队列,说是说队列,但是其实它中的每一个插入操作必须等待另一个线程移除完才能操作。所以说这个队列内部其实是没有任何一个元素的

接下来我们看下executorService().execute(call);这个execute方法

    public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    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);
}

源码在ThreadPoolExecutor中。他分为三种状态:

Question:如果我一次发起大量的请求就会造成大量的线程冗余。所以光有线程池控制是不够的。线程池只起了一个辅助作用,用来缓存线程,而真正对线程数量进行控制以及他们的执行时机进行调度的是我们的Dispatcher

Dispatcher的作用:

我们看到Dispatcher内部维护了3个队列

他会判断当前的最大请求数是否在允许范围内,以及最大的主机请求数是否在允许范围内。如果是的话他就会将这个请求扔到异步请求队列中执行,然后开启相应线程开启网络请求,如果不符合,他会把这个请求放到异步等待队列当中。等到异步执行队列当中有空闲的线程,他会从异步等待队列中去获取想要执行的网络请求。

我们在看AsyncCall这个类中的execute方法

 @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        eventListener.callFailed(RealCall.this, e);
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
 }

我们可以看到不管成功还是失败他最后都会调用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();
  }
}

我们看见他会调用一个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.
  }
}

我们来看一下这个方法,他首先会判断这个正在运行的异步请求数量是否超过他最大的允许请求数量。如果超过了就返回。
我们再看下一句,如果异步等待队列中是空的话。这时候也直接返回。
之后他就会通过迭代器去循环遍历异步等待队列。判断所有的运行的主机是否小于最大限制。他会将call从等待队列中移除,然后添加到正在执行的队列当中。最后开启线程池去执行我们的请求。

综上所述

我们的Dispatcher配置器 默认情况下会给我们每个域名有一个快速响应的请求数量,但是他还是限制了每个域名的并发数和总体的并发数。线程池做的 仅仅只是缓存线程的功能,真正调度策略还是靠我们的Dispatcher。我们可以根据自己的应用场景去配置我们的Dispatcher

上一篇下一篇

猜你喜欢

热点阅读