OKHttp开源框架学习三:任务调度核心类Dispatcher

目录

系列文章:

参考文章:

Dispatcher的作用

几个重要变量:

第一个,executorService

第二个,runningAsyncCalls

第三个,readyAsyncCalls

总结:


系列文章:

OKHttp开源框架学习一:同步请求总结

OKHttp开源框架学习二:异步请求总结

OKHttp开源框架学习三:任务调度核心类Dispatcher

OKHttp开源框架学习四:拦截器

OKHttp开源框架学习五:拦截器之RetryAndFollowUpInterceptor

OKHttp开源框架学习六:拦截器之BridgeInterceptor

OKHttp开源框架学习七:缓存策略源码分析

OKHttp开源框架学习八:拦截器之CacheInterceptor

OKHttp开源框架学习九:拦截器之ConnectInterceptor

OKHttp开源框架学习十:ConnectionPool连接池

OKHttp开源框架学习十一:拦截器之CallServerInterceptor

Okhttp总结

参考文章:

OKHttp源码解析 3:任务调度核心类dispatcher解析

在前面我们对 OKHttp 的同步与异步请求的分析中,我们已经提到过 Dispatcher 这个分发器类,而且在最后还总结了一个流程图,现在我们看一下这个流程图 

我们可以很明显的看到,OKHttp的所有 Call 请求,不管是同步请求 RealCall 还是异步请求 AsyncCall,下一步经过的都是 Dispatcher 这个类。从这里我们可以得出一个结论,那就是:它负责管理所有的 OKHttp 请求。 

Dispatcher的作用

维护请求Call的状态,并维护一个线程池,用于执行请求。发送的同步/异步请求都会在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<>();

这段源码我们在上一篇文章中已经讲解过。很明显,Dispatcher 内部维护着三个队列:同步请求队列 runningSyncCalls异步请求队列 runningAsyncCalls异步缓存队列 readyAsyncCalls,和一个线程池 executorService。 

第一个,executorService

这个就是Dispatcher自己维护并使用的线程池,具体可看:

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

在这里,我们看到了ThreadPoolExecutor,看它的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

从它的名字和构造方法我们就可以看出来这是一个线程池。

第二个,runningAsyncCalls

队列,用于维护正在运行的异步请求,包含没有被finished但是被用户取消的异步请求

第三个,readyAsyncCalls

队列,用于维护准备就绪的异步请求

再看下面的源码段,在同步请求中,Dispatcher 只是把 Call 请求添加到同步请求队列中,非常简单有没有,Dispatcher 对同步请求就只是做了这一个操作,重点在异步请求。

//OKHttp的同步请求,只是单纯的把 Call 请求添加到同步请求队列中,交给线程池去执行
//线程不安全的,使用 synchronized 关键字锁住
synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

...

//异步请求方法,线程不安全的,使用 synchronized 关键字锁住
synchronized void enqueue(AsyncCall call) {
    //判断正在执行的异步请求是否小于允许的最大值、以及正在执行的异步请求的 Host 是否小于允许的最大值
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //条件允许,则把 Call 请求加入到正在执行的请求队列中
      runningAsyncCalls.add(call);
      //通知线程池执行这个请求
      executorService().execute(call);
    } else {
      //条件不允许,则把 Call 请求加入到异步等待队列中
      readyAsyncCalls.add(call);
    }
  }

在异步请求中,先是判断正在执行的异步请求队列 size 是否小于允许的最大值(64)、以及正在执行的异步请求队列 size的 Host 是否小于允许的最大值(5),如果为 true 就把当前的 Call 请求加入到异步请求队列中,为 false 的话,加入到 异步等待队列中。到这里,我们也就验证了前面说的观点:Dispatcher负责管理所有的 OKHttp 请求。

接下来,维护了请求之后,肯定还需要去执行这些请求,所以这个时候我们就要讲到线程池了。在上面的异步请求源码段中,是通过这段executorService().execute(call);去执行请求的,我们看一下 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;
  }

 

很明显,这里主要就是实例化线程池对象,注意前面三个参数的传值。前面三个参数的意思分别为:池最小值、池最大值、请求存活时间,这里分别传入的是:0、Integer 最大值、60。可能有人会对第二个参数的值有疑问,为什么要传入 Integer 最大值,这不是产生大量线程消耗资源嘛?其实并不会这样,我们要注意上面讲异步请求入队列的判断。就是当当前正在执行的异步请求队列的大小小于64时,才会加入到队列中,否则只会加入到等待队列中。所以线程池的池最大值传入 Integer 的最大值并不会造成大量资源占用,相反,还能方便我们根据实际情况自由调节请求队列的大小。

线程池我们弄清楚了,然后就是怎么去执行请求了。我们知道线程的运行会调用 run() 方法。上一篇我们已经提到过,AsyncCall 是一个 Runnable 的实现类,它的 run() 方法实现在 NamedRunnable 类中,这个 run() 方法又调用了一个抽象方法 execute(),而 AsyncCall 又实现了这个抽象方法。绕了一圈又绕了回来,所以最后的执行还是得看 AsyncCall 的 execute() 方法,源码如下:
 

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //通过拦截链获取 Response
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          //返回 Callback 结果
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          //返回 Callback 结果
          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 {
        //主动finish掉请求
        client.dispatcher().finished(this);
      }
    }

源码我们在上一篇文章中也已经分析过了,主要就是通过拦截链获取 Response,然后根据判断返回 Callback,最后在 finally 中主动 finish 掉请求。讲到这里,我们已经把线程池、异步请求队列都讲到了,但是还有一个等待请求队列没有提到。那到底什么情况下才能使用到这个队列呢?我们大概猜想:当异步请求队列有空闲位置的时候,是不是就会把等待队列中的请求添加进去呢?我们来验证一下我们的想法,继续跟踪代码。

这个时候我们就要看一下,每次 finally 里面 finish 掉请求到底做了什么,我们看源码

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!");
      //当是异步请求调用 finish 方法时,会执行到这里
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

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

这段源码段我们在前面也分析过,但是有一段我们没有说明,那就是 promoteCalls() 这一句。在异步请求中,我们的 finish 方法才会传入 true,执行到这一句,这里我们来重点讲解一下这个方法,看源码

private void promoteCalls() {
    //当正在请求队列的 size 大于或等于允许的最大值时,直接返回
    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();

      //当请求的 正在执行请求的host小于允许host的最大值时
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        //把请求从等待队列中移除
        i.remove();
        //添加到请求队列中
        runningAsyncCalls.add(call);
        //线程池执行请求
        executorService().execute(call);
      }

      当正在请求队列的 size 大于或等于允许的最大值时,直接返回
      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

代码注释我已经写的非常详细了,很明显,这个方法就是起到了一个调整队列的作用,当异步请求队列有空闲位置时,就会从等待队列中添加一个请求到异步请求队列中,这样整个逻辑就完善了,Dispatcher 的主要内容也就都提及了。讲到这里, Dispatcher 是什么我们应该已经很清楚了,一句话总结:Dispatcher 通过维护一个线程池,来维护、管理、执行OKHttp的请求。

总结:

Dispatcher 通过维护一个线程池,来维护、管理、执行OKHttp的请求。
Dispatcher 内部维护着三个队列:同步请求队列 runningSyncCalls、异步请求队列 runningAsyncCalls、异步缓存队列 readyAsyncCalls,和一个线程池 executorService。
Dispatcher类整体可以参照生产者消费者模式来理解:
Dispatcher是生产者,executorService是消费者池,runningSyncCalls、runningAsyncCalls和readyAsyncCalls是消费者,用来消费请求Call。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值