线程池常用于需要频繁创建和销毁线程的情况,因为创建和销毁线程都需要消耗时间,如果经常性这样操作,对于性能肯定是非常有影响的,ThreadPoolExecutor能够帮助我们创建线程池,帮助我们管理维护线程,先看看ThreadPoolExecutor在OkHttp中的使用

1
2
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

分析下在创建ThreadPoolExecutor时的几个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

corePoolSize:线程池中的核心线程数,除非设置了allowCoreThreadTimeOut,否则它们一旦创建就不会被销毁(the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set)

maximumPoolSize:线程池中的最大线程数量(the maximum number of threads to allow in the pool)

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.)

unit:keepAliveTime最大限制时间的单位(the time unit for the {@code keepAliveTime} argument)

workQueue:线程等待执行的队列,常见的有ArrayBlockingQueue(基于数组实现的 FIFO 阻塞队列),LinkedBlockingQueue(基于链表实现的 FIFO 阻塞队列),SynchronousQueue(没有任何容量的阻塞队列),PriorityQueue(具有优先级的无限阻塞队列)(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.)

threadFactory:创建线程的工厂,当这个参数不传时,会用默认的DefaultThreadFactory作为线程创建工厂(the factory to use when the executor creates a new thread)

handler:默认是new AbortPolicy();,当任务队列已满并且无法创建新线程的时候会抛RejectedExecutionException异常(the handler to use when execution is blocked because the thread bounds and queue capacities are reached)

要提交任务只需调用execute(Runnable runnable)方法即可。关闭线程池有两种方法,shutdown()会中断没有正在执行任务的线程,正在执行的不中断,shutdownNow()会中断所有任务的线程。

线程池的执行流程大致是这样的:,当我们提交任务时,先去判断核心线程池是否已满,若未达到corePoolSize,那么会创建一个新线程执行任务,如果已经达到了核心线程数,那么便判断工作队列是否已满,如果没有满,那么将任务放入工作队列中,满了则继续下一个判断,即判断线程池的数量是否已经超过最大线程数maximumPoolSize,没有超过则创建一个新线程执行任务,如果超过的话就执行线程饱和策略。

除了自己创建线程池外,Executors也提供了四种快速创建线程池的方法:

Executors.newFixedThreadPool();

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

newFixedThreadPool创建了一个仅含有nThreads个核心线程的线程池,当核心线程池满时,新提交的任务会放入一个无大小限制的阻塞队列。

Executors.newSingleThreadExecutor();

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

newSingleThreadExecutor创建了只允许有一个线程的线程池,如果线程池已满,那么会把新提交的任务放入一个无大小限制的阻塞队列中,这些任务会顺序执行,不会产生并发问题。

Executors.newCachedThreadPool();

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

newCachedThreadPool创建的线程池没有大小限制,任何新提交的任务都会新创建线程执行,处于闲置状态的线程在 60s 后将会被销毁,OkHttp创建线程池的策略和这个相同,只不过OkHttp自己定义了一个创建线程的工厂。

Executors.newScheduledThreadPool();

1
2
3
4
5
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}

newScheduledThreadPool创建的线程池核心线程数由自己定义,线程池大小没有任何限定,闲置线程只能存活 10 毫秒,几乎一闲置就会被回收,这种方式创建的线程池还可以执行定时任务。

创建线程池的策略大致如此,如果是 CPU 密集型任务,那么配置 CPU 核数 + 1 个线程数的线程池,如果是 IO 密集型,那么配置 CPU 核数 * 2 个线程数的线程池。