Hello, everyone. I’m little little deer, a vegetable chicken Android app ape. Recently I have been reading the Glide source code, and today we are going to look at the configuration of the Glide thread pool. There are two main goals for this code reading

  1. Figure out how Glide does thread pool configuration
  2. How does Glide load priority

Glide is used to load images. We know that when a page is paused, It can suspend requests for the current page based on the life cycle of the page, but how does it load images if the current page slides a lot of images? Does the first call load come first or the last call load first? What does Glide do if some images on a page need to be loaded first?

Glide thread pool use

Glide DecodeJob ResourceCacheGenerator > DataCacheGenerator > SourceGenerator Three process changes. This process change involves the use of two thread pools.

  1. EngineJob#start starts the request

    public synchronized void start(DecodeJob<R> decodeJob) { this.decodeJob = decodeJob; / / if get photo from the cache using diskCacheExecutor GlideExecutor executor. = decodeJob willDecodeFromCache ()? diskCacheExecutor : getActiveSourceExecutor(); executor.execute(decodeJob); } private GlideExecutor getActiveSourceExecutor () {/ / if useUnlimitedSourceGeneratorPool to true use unlimited thread pool / / if useAnimationPool to true and if useUnlimitedSourceGeneratorPool to false use animation thread pool Otherwise, use sourceExecutor return useUnlimitedSourceGeneratorPool ? sourceUnlimitedExecutor : (useAnimationPool ? animationExecutor : sourceExecutor); }Copy the code
  2. EngineJob#reschedule reschedules

    @Override public void reschedule(DecodeJob<? GetActiveSourceExecutor ().execute(job); }Copy the code

Glide thread pool configuration

Glide Excutor parameter initialization comes from GlideBuilder#build and these come from GlideExecutor without additional Settings. All thread pools for GlideExecutor are done by configuring ThreadPoolExecutor.

I met ThreadPoolExecutor

ExecutorService is the original thread pool interface, and the ThreadPoolExecutor class is a concrete implementation of the thread pool, using constructors to configure the parameters of the thread pool.

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
Copy the code

Parameter Description:

CorePoolSize, the number of core threads in the thread pool. By default, it exists even if the core thread has no task executing. We fix a certain number of core threads and it stays alive so that we avoid the overhead of the normal CPU creating and destroying threads. If we set the allowCoreThreadTimeOut property of ThreadPoolExecutor to true, then idle core threads will have a timeout policy set by keepAliveTime. That is, if the core thread does not respond within keepAliveTime, the thread is terminated. AllowCoreThreadTimeOut The default is false and the core thread has no timeout. MaximumPoolSize, the maximum number of threads in the thread pool. If the number of tasks exceeds the maximum number of threads, other tasks may be blocked. Maximum number of threads = core threads + non-core threads. Non-core threads are created only when the core thread is not available and the thread pool is free, and are destroyed after the task is completed. KeepAliveTime: Specifies the timeout period for non-core threads. When idle time exceeds this timeout, non-core threads are reclaimed. This property also applies to core threads when allowCoreThreadTimeOut is set to true. Unit, enumeration TimeUnit, TimeUnit. WorkQueue, the task queue in the thread pool, the runnable that we submit to the thread pool is stored on this object. Thread pool allocation follows this rule:

When the number of core threads in the thread pool does not reach the maximum number of threads, a core thread is started to execute the task. If the number of core threads in the thread pool reaches the maximum number of threads, the task will be inserted into the task queue and queued for execution. If in the previous step the task queue was full but the number of threads in the thread pool did not reach the limit number of threads, then a non-core thread is started to process the task; If the number of threads reached the limit in the previous step, the thread pool rejects the task and ThreadPoolExecutor calls the rejectedExecution method of RejectedtionHandler to notify the caller.

ThreadFactory: threadFactory that provides thread pools with the ability to create new threads.

DiskCacheExecutor configuration process

GlideExecutor provides three methods to create DiskCacheExecutor, all of which end up calling the one with three arguments

public static GlideExecutor newDiskCacheExecutor(
    int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
  return new GlideExecutor(
      new ThreadPoolExecutor(
          threadCount /* corePoolSize */,
          threadCount /* maximumPoolSize */,
          0 /* keepAliveTime */,
          TimeUnit.MILLISECONDS,
          new PriorityBlockingQueue<Runnable>(),
          new DefaultThreadFactory(name, uncaughtThrowableStrategy, true)));
}
Copy the code

DiskCacheExecutor is a core thread with 1, there is no pool of non-core threads, and all tasks are executed sequentially in the pool. The store object for Runnable is PriorityBlockingQueue.

The SourceExecutor configuration process

public static GlideExecutor newSourceExecutor() {
  return newSourceExecutor(
      calculateBestThreadCount(),
      DEFAULT_SOURCE_EXECUTOR_NAME,
      UncaughtThrowableStrategy.DEFAULT);
}
​
 public static GlideExecutor newSourceExecutor(
      int threadCount, String name, UncaughtThrowableStrategy uncaughtThrowableStrategy) {
    return new GlideExecutor(
        new ThreadPoolExecutor(
            threadCount /* corePoolSize */,
            threadCount /* maximumPoolSize */,
            0 /* keepAliveTime */,
            TimeUnit.MILLISECONDS,
            new PriorityBlockingQueue<Runnable>(),
            new DefaultThreadFactory(name, uncaughtThrowableStrategy, false)));
  }
Copy the code

You can see that the SourceExecutor build process is basically the same, except that the number of core threads is dynamically counted through calculateBestThreadCount.

If (bestThreadCount == 0) {// If the number of CPU cores exceeds 4, the number of core threads is 4. If the number of CPU cores is less than 4, the number of core threads is used as the number of core threads Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, RuntimeCompat.availableProcessors()); } return bestThreadCount;Copy the code

UnlimitedSourceExecutor UnlimitedPool of threads

public static GlideExecutor newUnlimitedSourceExecutor() {
  return new GlideExecutor(new ThreadPoolExecutor(
      0,
      Integer.MAX_VALUE,
      KEEP_ALIVE_TIME_MS,
      TimeUnit.MILLISECONDS,
      new SynchronousQueue<Runnable>(),
      new DefaultThreadFactory(
          SOURCE_UNLIMITED_EXECUTOR_NAME,
          UncaughtThrowableStrategy.DEFAULT,
          false)));
}
Copy the code

UnlimitedSourceExecutor has no core threads and the number of non-core threads is infinite.

AnimationExecutor

public static GlideExecutor newAnimationExecutor() { int bestThreadCount = calculateBestThreadCount(); int maximumPoolSize = bestThreadCount >= 4 ? 2:1; return newAnimationExecutor(maximumPoolSize, UncaughtThrowableStrategy.DEFAULT); } public static GlideExecutor newAnimationExecutor( int threadCount, UncaughtThrowableStrategy uncaughtThrowableStrategy) { return new GlideExecutor( new ThreadPoolExecutor( 0 /* corePoolSize */, threadCount, KEEP_ALIVE_TIME_MS, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(), new DefaultThreadFactory( ANIMATION_EXECUTOR_NAME, uncaughtThrowableStrategy, true))); }Copy the code

AnimationExecutor has no core threads. The number of non-core threads is determined by the number of Cpu cores. The number of non-core threads is 2 if the number of Cpu cores is greater than equal 4, and 1 otherwise.

Glide thread pool summary

DiskCacheExecutor and SourceExecutor use a fixed number of core threads and are suitable for processing CPU-intensive tasks, but there are no non-core threads. Ensure that the CPU is allocated as few threads as possible in cases where it is used by workers for long periods of time, i.e. for long periods of time.

UnlimitedSourceExecutor uses non-core threads. Non-core threads are infinitely large and are suitable for executing a large number of small, short-term tasks concurrently. Very few resources are consumed in idle time.

AnimationExecutor has no core threads and has a limited number of non-core threads. The difference with UnlimitedSourceExecutor is that the number of core threads does not match the number of work queues. First time I’ve seen it used like this.

How does Glide implement load priority

All but UnlimitedSourceExecutor are PriorityBlockingQueue. PriorityBlockingQueue is an unbounded blocking queue with a priority. In other words, the higher the priority, the earlier the execution.

We know that image loading is performed in a thread pool with a DecodeJob that implements the Runnable and Comparable interfaces. When a DecodeJob is submitted to the thread pool, the DecodeJob priority is compared through compareTo if it needs to be added to the work queue

@Override public int compareTo(@NonNull DecodeJob<? Int result = getPriority() -other.getpriority (); Order order is an incremented int every time a DecodeJob is initialized ++ so the DecodeJob initialized later has a higher Priority than the DecodeJob initialized first. if (result == 0) { result = order - other.order; } return result; }Copy the code

\