OkHttp源码解析(二)

7 篇文章 0 订阅
1 篇文章 0 订阅

今天给大家带来的是Okhttp源码解析的第二篇文章。
本篇文章的主要内容就是对异步调用的一个流程以及他的原理弄清楚。在进行异步解析之前,我们先来回顾一下第一篇文章中的同步的原理。在同步中,我们首先是对各种参数进行配置以及添加,然后组成我们的请求request,同样的,异步开始的步骤也是这样的。不同点就是异步请求在调用的时候需要在线程池中对各个线程进行管理以及资源分配。在同步中,我们知道最后异步是调用的excute()方法,然后我们进入到excute()方法中去,

public Response execute() throws IOException {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        try {
          client.getDispatcher().executed(this);
          Response result = getResponseWithInterceptorChain(false);
          if (result == null) throw new IOException("Canceled");
          return result;
        } finally {
          client.getDispatcher().finished(this);
        }     }

我们发现获取response的方法是getResponseWithInterceptorChain(false)。好,暂时先回忆到这里,等会分析完异步就会发现神奇的事情(卖个关子先,^-^)。现在我们正式进入到我们的异步源码分析中来。我们知道异步最后调用的方法是enqueue(Call call),OK,我们进入到该方法瞧一瞧,

public void enqueue(Callback responseCallback) {
        enqueue(responseCallback, false);
      }

      void enqueue(Callback responseCallback, boolean forWebSocket) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
      }

发现之后调用的是下面一个enqueue();而在该方法中处理callback的是client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket))这段代码,在这里,我们首先弄清楚这个方法中的参数是什么,因为这在后面中很重要,发现是一个AsynsCall类,发现他继承自NamedRunnable,而这个NamedRunnable又是什么呢,继续跟进,发现它是继承自Runnable,也就是说他将会开启一个线程了,正好,这也契合了我们这个异步的说法了。我们看看NamedRunnable这个类,

public abstract class NamedRunnable implements Runnable {
      protected final String name;

      public NamedRunnable(String format, Object... args) {
        this.name = String.format(format, args);
      }

      @Override public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(name);
        try {
          execute();
        } finally {
          Thread.currentThread().setName(oldName);
        }
      }

      protected abstract void execute();
    }

我们发现在run()方法中执行了抽象方法execute();所以我们明白,当线程开启了之后,首当其冲的就是来执行这个excute()方法了。而这个方法是抽象的,所以这个具体要执行的内容就是他的子类的execute()方法了。好,回到这里来,client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));我们知道了他的参数是什么了,再看看是谁调用了他,我们发现是client.getDispatcher(),很明显了,就是Dispatcher调用了它,我们再进入到Dispatcher的enqueue()方法中去。

synchronized void enqueue(AsyncCall call) {
        if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
          runningCalls.add(call);
          getExecutorService().execute(call);
        } else {
          readyCalls.add(call);
        }
      }

看到这个方法加了同步锁,所以说明这个方法在每次只能有一个请求来调用。他的意思是首先判断总的请求个数是否小于我们设定的最大的请求数目(最多为64个),再判断正在进行请求的请求个数是否小于我们设定的最大的同时请求的个数,如果条件都满足,那么将我们的请求添加到正在请求的队列中,并且执行这个请求,如果不满足,那么就将这个请求添加到准备请求的队列中去,进行等待。所以,此时我们关注的是getExecutorService().execute(call)这行代码。getExecutorService()看起来像是获取到一个执行call的服务,我们进去看看,

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

它返回的是一个ThreadPoolExecutor对象,这下我们明白了,它是一个线程池对象,它的作用就是来管理并执行线程池中的每一个线程,所以看看ThreadPoolExecutor类的excute()方法了,

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

这个方法的流程是
1、如果线程池的当前大小还没有达到基本大小(poolSize < corePoolSize),那么就新增加一个线程处理新提交的任务;
2、如果当前大小已经达到了基本大小,就将新提交的任务提交到阻塞队列排队,等候处理workQueue.offer(command);
3、如果队列容量已达上限,并且当前大小poolSize没有达到maximumPoolSize,那么就新增线程来处理任务;
4、如果队列已满,并且当前线程数目也已经达到上限,那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。至于如何拒绝处理新增
在这里我们需要理解几个属性的作用:

corePoolSize:
线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。

maximumPoolSize:
线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。

allowCoreThreadTimeOut:
该属性用来控制是否允许核心线程超时退出。如果线程池的大小已经达到了corePoolSize,不管有没有任务需要执行,线程池都会保证这些核心线程处于存活状态。可以知道:该属性只是用来控制核心线程的。

所以接下来会进入到addWorker()方法中去,

private boolean addWorker(Runnable firstTask, boolean core) {
        ...
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

在这个方法中只分析重点部分,他首先将我们传递过来的runnable放入到thread中,因为带个if中的条件经过一些列计算都是成立的,所以进入到if中,在这里我们看到largestPoolSize = s;也就是最大的线程执行数,并且workerAdded = true; 所以在下面的if中会进入并且执行这个start,前面我们分析了这个AsyncCall是一个runnable,所以当start之后,就会执行里面的run方法,而里面的run方法会执行execute()方法,我们进入到他的execute()方法中,

@Override 
   protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(originalRequest, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          Request request = engine == null ? originalRequest : engine.getRequest();
          responseCallback.onFailure(request, e);
        }
      } finally {
        client.getDispatcher().finished(this);
      }
    }

看到了我们很熟悉的代码了,也就是Response response = getResponseWithInterceptorChain(forWebSocket);还记得之前在同步中最后也是执行到这里,然后去进行 网络请求了。 之后的分析在上一篇已经有了,如果还不清楚的,可以去OkHttp源码解析(一)了解。

总结,本篇文章主要就异步的调用进行了一个整体的分析,从分析中也透露出了他的异步的特性了,在我们每次请求的时候,我们都要对每个请求在线程池中进行线程的分配以保证我们的每个请求可以顺利的进行,而在这一系列的请求中,ThreadPoolExecutor是至关重要的,因为他是管理线程的线程池,线程的分配都由他来进行操作。总的来说,OKHttp异步请求在某些地方分析起来还是有些吃力的。希望能给大家带来帮助,如果文章有出入的地方还请大家指正,谢谢!!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值