Okhttp源码分析(一)线程调度和任务队列

Okhttp提供两种请求方式,同步(execute)和异步(enqueue)。同步请求会阻塞当前线程,日常开发基本不会用到。异步请求会创建新线程,管理线程和任务逻辑主要在Dispatcher类里,接下来重点分析这个类,源码版本是3.14.9。

1.线程池

如果要管理线程,一定会用到线程池。我们先重温下线程池的作用

  • 复用线程,降低系统资源的使用
  • 提高响应速度,减少创建线程的时间
  • 管理线程,避免无限创建线程,导致系统崩溃

 Dispatcher创建线程池的地方

 可以通过构造方法传入自己的线程池,不传的情况就会使用默认的。这个默认的线程池构建不只是类似,和Executors.newCachedThreadPool()是一模一样的,只是改变了threadFactory。

 用的是无界队列,最大线程数也是等同于无限。这就涉及一个问题了,怎么避免无限创建线程,毕竟项目中接口的请求是很频繁的,创建线程过多,有可能会导致崩溃。关键就在任务队列的限制。

2.任务队列

除了线程池里面的队列,Dispatcher还另外创建了三个队列

 readyAsyncCalls是异步等待队列,runningAsyncCalls是异步执行中的队列,runningSyncCalls是同步执行中的队列。我们先从执行入口enqueue(AsyncCall call)开始分析:

 首先会把任务加入等待队列中。findExistingCallWithHost(String host)从readyAsyncCalls和runningAsyncCalls找出和当前任务host服务器地址一样的任务。reuseCallsPerHostFrom(AsyncCall other)复用同一个AtomicInteger来计算每个服务器有多少个任务,后面promoteAndExecute会用到这个callsPerHost会用于判断是否超出服务器最大请求数。

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));

    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();

        if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

    return isRunning;
  }

promoteAndExecute方法的作用是把符合条件的等待任务加入运行队列和线程池执行。判断每个服务器最大请求数前,还判断了所有服务器总请求数限制。 

有了maxRequests和maxRequestsPerHost限制,线程池就不会无限创建线程。

任务结束,移出运行队列

 finished递减服务器请求数,calls.remove(call)移出运行队列。运行队列有闲置,所以再执行一遍promoteAndExecute(),把等待中的任务允许起来。还会触发回调idleCallback,意图是通知有任务结束,idleCallback默认是空的,需要我们传进去,不过实际作用不大,可以忽略。

调用finished的地方是在Runnable的finally语块里,保证了线程异常也能移出队列。

总结

  1. 我们可以根据项目需求,设置最大请求数和异步线程池。如果项目的服务器单一,建议把maxRequestsPerHost设置大一些,默认是5。
  2. Dispatcher应该设置成单例,多个okhttpClient复用同一个Dispatcher,这样maxRequestsPerHost和maxRequests才有意义,也不会创建多个线程池浪费系统资源。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值