線程池與Glide網(wǎng)絡(luò)請求并發(fā)調(diào)度

Executors類提供了4種不同的線程池:newCachedThreadPool绩蜻、newFixedThreadPool缺狠、 newScheduledThreadPool和newSingleThreadExecutor,它們都是直接或間接通過ThreadPoolExecutor實(shí)現(xiàn)的对蒲。
*ThreadPoolExecutor:

    // Public constructors and methods

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

ThreadPoolExecutor的構(gòu)造方法有以下幾個(gè)重要參數(shù):
corePoolSize:核心線程數(shù)赚导。核心線程會(huì)在線程池中一直存活蝗蛙,即時(shí)它們處于閑置狀態(tài),例外情況是allowCoreThreadTimeOut被設(shè)置為true敲长。
maximumPoolSize:最大線程數(shù)郎嫁。
keepAliveTime:線程閑置時(shí)的超時(shí)時(shí)長秉继,超時(shí)后線程會(huì)被回收。
unit:keepAliveTime的時(shí)間單位泽铛。
workQueue:存放等待執(zhí)行的任務(wù)的阻塞隊(duì)列尚辑。
隊(duì)列與線程池按照以下規(guī)則進(jìn)行交互:
如果運(yùn)行的線程數(shù)小于核心線程數(shù)(corePoolSize),則首選添加新線程而不排隊(duì)盔腔。如果運(yùn)行的線程數(shù)等于或者大于核心線程數(shù)(corePoolSize)杠茬,則首選將請求加入隊(duì)列而不添加新線程。如果請求無法加入隊(duì)列弛随,則創(chuàng)建新線程瓢喉;如果這將導(dǎo)致超出最大線程數(shù)(maximumPoolSize),則任務(wù)將被拒絕執(zhí)行舀透。
*Executors:

    /**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可以看到FixedThreadPool只有固定數(shù)量的核心線程栓票,任務(wù)隊(duì)列是基于鏈表的無界阻塞隊(duì)列。當(dāng)所有線程都在運(yùn)行時(shí)愕够,新任務(wù)都會(huì)放到任務(wù)隊(duì)列中等待逗载。
默認(rèn)情況下,Glide的網(wǎng)絡(luò)請求是在EngineJob中的sourceExecutor中執(zhí)行的链烈,而這個(gè)sourceExecutor是通過GlideExecutor的newSourceExecutor方法實(shí)例化的厉斟。
*GlideExecutor:

/**
   * Returns a new fixed thread pool with the default thread count returned from
   * {@link #calculateBestThreadCount()}, the {@link #DEFAULT_SOURCE_EXECUTOR_NAME} thread name
   * prefix, and the
   * {@link com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy#DEFAULT}
   * uncaught throwable strategy.
   *
   * <p>Source executors allow network operations on their threads.
   */
  public static GlideExecutor newSourceExecutor() {
    return newSourceExecutor(calculateBestThreadCount(), DEFAULT_SOURCE_EXECUTOR_NAME,
        UncaughtThrowableStrategy.DEFAULT);
  }

  /**
   * Returns a new fixed thread pool with the given thread count, thread name prefix,
   * and {@link com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy}.
   *
   * <p>Source executors allow network operations on their threads.
   *
   * @param threadCount The number of threads.
   * @param name The prefix for each thread name.
   * @param uncaughtThrowableStrategy The {@link
   * com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy} to use to
   *                                  handle uncaught exceptions.
   */
  public static GlideExecutor newSourceExecutor(int threadCount, String name,
      UncaughtThrowableStrategy uncaughtThrowableStrategy) {
    return new GlideExecutor(threadCount, name, uncaughtThrowableStrategy,
        false /*preventNetworkOperations*/, false /*executeSynchronously*/);
  }

  // Visible for testing.
  GlideExecutor(int poolSize, String name,
      UncaughtThrowableStrategy uncaughtThrowableStrategy, boolean preventNetworkOperations,
      boolean executeSynchronously) {
    this(
        poolSize /* corePoolSize */,
        poolSize /* maximumPoolSize */,
        0 /* keepAliveTimeInMs */,
        name,
        uncaughtThrowableStrategy,
        preventNetworkOperations,
        executeSynchronously);
  }

  GlideExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTimeInMs, String name,
      UncaughtThrowableStrategy uncaughtThrowableStrategy, boolean preventNetworkOperations,
      boolean executeSynchronously) {
    this(
        corePoolSize,
        maximumPoolSize,
        keepAliveTimeInMs,
        name,
        uncaughtThrowableStrategy,
        preventNetworkOperations,
        executeSynchronously,
        new PriorityBlockingQueue<Runnable>());
  }

  GlideExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTimeInMs, String name,
      UncaughtThrowableStrategy uncaughtThrowableStrategy, boolean preventNetworkOperations,
      boolean executeSynchronously, BlockingQueue<Runnable> queue) {
    super(
        corePoolSize,
        maximumPoolSize,
        keepAliveTimeInMs,
        TimeUnit.MILLISECONDS,
        queue,
        new DefaultThreadFactory(name, uncaughtThrowableStrategy, preventNetworkOperations));
    this.executeSynchronously = executeSynchronously;
  }

  /**
   * Determines the number of cores available on the device.
   *
   * <p>{@link Runtime#availableProcessors()} returns the number of awake cores, which may not
   * be the number of available cores depending on the device's current state. See
   * http://goo.gl/8H670N.
   */
  public static int calculateBestThreadCount() {
    // We override the current ThreadPolicy to allow disk reads.
    // This shouldn't actually do disk-IO and accesses a device file.
    // See: https://github.com/bumptech/glide/issues/1170
    ThreadPolicy originalPolicy = StrictMode.allowThreadDiskReads();
    File[] cpus = null;
    try {
      File cpuInfo = new File(CPU_LOCATION);
      final Pattern cpuNamePattern = Pattern.compile(CPU_NAME_REGEX);
      cpus = cpuInfo.listFiles(new FilenameFilter() {
        @Override
        public boolean accept(File file, String s) {
          return cpuNamePattern.matcher(s).matches();
        }
      });
    } catch (Throwable t) {
      if (Log.isLoggable(TAG, Log.ERROR)) {
        Log.e(TAG, "Failed to calculate accurate cpu count", t);
      }
    } finally {
      StrictMode.setThreadPolicy(originalPolicy);
    }

    int cpuCount = cpus != null ? cpus.length : 0;
    int availableProcessors = Math.max(1, Runtime.getRuntime().availableProcessors());
    return Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, Math.max(availableProcessors, cpuCount));
  }

GlideExecutor的newSourceExecutor與Executors的newFixedThreadPool類似,都是固定大小的線程池强衡,不過任務(wù)隊(duì)列不同擦秽。線程池大小為calculateBestThreadCount,該值為設(shè)備可用核心數(shù)但最大不超過4漩勤。任務(wù)隊(duì)列為PriorityBlockingQueue感挥,一種基于優(yōu)先級的無界阻塞隊(duì)列,插入元素需要實(shí)現(xiàn)Comparable接口的compareTo方法來提供排序依據(jù)越败。
*DecodeJob:

  @Override
  public int compareTo(DecodeJob<?> other) {
    int result = getPriority() - other.getPriority();
    if (result == 0) {
      result = order - other.order;
    }
    return result;
  }

Glide的Runnable實(shí)現(xiàn)類是DecodeJob触幼,它的compareTo方法的邏輯是:優(yōu)先級(共IMMEDIATE/HIGH/NORMAL/LOW四種,依次降低究飞,默認(rèn)為NORMAL)高的優(yōu)先置谦,若優(yōu)先級相同則順序在前的優(yōu)先(先進(jìn)先出)。
*RequestBuilder:

  /**
   * Set the target the resource will be loaded into.
   *
   * @param target The target to load the resource into.
   * @return The given target.
   * @see RequestManager#clear(Target)
   */
  public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
    Util.assertMainThread();
    Preconditions.checkNotNull(target);
    if (!isModelSet) {
      throw new IllegalArgumentException("You must call #load() before calling #into()");
    }

    Request previous = target.getRequest();

    if (previous != null) {
      requestManager.clear(target);
    }

    requestOptions.lock();
    Request request = buildRequest(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
  }

*HttpUrlFetcher:


  @Override
  public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    final InputStream result;
    try {
      result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
          glideUrl.getHeaders());
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
      return;
    }

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
          + " ms and loaded " + result);
    }
    callback.onDataReady(result);
  }

  private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      // Comparing the URLs using .equals performs additional network I/O and is generally broken.
      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (statusCode / 100 == 3) {
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == -1) {
      throw new HttpException(statusCode);
    } else {
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

  @Override
  public void cancel() {
    // TODO: we should consider disconnecting the url connection here, but we can't do so
    // directly because cancel is often called on the main thread.
    isCancelled = true;
  }

Glide將加載請求和Target(ImageView)關(guān)聯(lián)亿傅,開始某個(gè)ImageView的加載請求前會(huì)先將該ImageView關(guān)聯(lián)的請求清除媒峡。此時(shí)在線程池中的關(guān)聯(lián)的DecodeJob,正在進(jìn)行的網(wǎng)絡(luò)請求不會(huì)被中斷葵擎,在等待隊(duì)列里的也不會(huì)被直接從線程池移除谅阿,而是移除回調(diào)并設(shè)置取消標(biāo)志位,讓未開始的后續(xù)加載步驟的邏輯不會(huì)被執(zhí)行。
當(dāng)列表(ListView/RecyclerView)快速滾動(dòng)時(shí)签餐,同時(shí)執(zhí)行的網(wǎng)絡(luò)請求數(shù)量不會(huì)超過設(shè)備可用核心數(shù)寓涨,其余請求會(huì)放到隊(duì)列中等待執(zhí)行。雖然隊(duì)列長度可能會(huì)一下增加到幾十氯檐,但隨著列表復(fù)用View缅茉,隊(duì)列中的大部分請求都會(huì)被取消掉,之后執(zhí)行時(shí)不會(huì)發(fā)起網(wǎng)絡(luò)請求男摧,并迅速讓位于等待中的請求蔬墩。也就是說,快速滾動(dòng)過程的中間很多個(gè)列表項(xiàng)的請求都會(huì)被略過耗拓。這樣的機(jī)制保證了不會(huì)過度消耗資源導(dǎo)致滑動(dòng)卡頓拇颅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市乔询,隨后出現(xiàn)的幾起案子樟插,更是在濱河造成了極大的恐慌,老刑警劉巖竿刁,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄锤,死亡現(xiàn)場離奇詭異,居然都是意外死亡食拜,警方通過查閱死者的電腦和手機(jī)鸵熟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來负甸,“玉大人流强,你說我怎么就攤上這事∩氪” “怎么了打月?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚕捉。 經(jīng)常有香客問我奏篙,道長,這世上最難降的妖魔是什么迫淹? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任秘通,我火速辦了婚禮,結(jié)果婚禮上千绪,老公的妹妹穿的比我還像新娘充易。我一直安慰自己,他們只是感情好荸型,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般瑞妇。 火紅的嫁衣襯著肌膚如雪稿静。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天辕狰,我揣著相機(jī)與錄音改备,去河邊找鬼。 笑死蔓倍,一個(gè)胖子當(dāng)著我的面吹牛悬钳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播偶翅,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼默勾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了聚谁?” 一聲冷哼從身側(cè)響起母剥,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎形导,沒想到半個(gè)月后环疼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朵耕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年炫隶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阎曹。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡等限,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芬膝,到底是詐尸還是另有隱情望门,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布锰霜,位于F島的核電站筹误,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏癣缅。R本人自食惡果不足惜厨剪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望友存。 院中可真熱鬧祷膳,春花似錦、人聲如沸屡立。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至勇皇,卻和暖如春罩句,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敛摘。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工门烂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兄淫。 一個(gè)月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓屯远,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捕虽。 傳聞我的和親對象是個(gè)殘疾皇子慨丐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容