前言
最近在看OkHttp的源码,看的时候发现有关线程池的运用,自己就仔细想了一下,这个块知识好像不是很牢固。没办法,再研究一下有关线程池的相关知识吧。学习就是一个查漏补缺的过程,最终的目的还是要形成自己的知识网络。
为什么要使用线程池
平时在Android开发的过程中经常会用到多线程异步处理相关任务,每开一个线程都要新建一个Thread对象来处理,这种操作会造成哪些后果呢?
由于多线程异步处理任务有可能造成这样或者那样的问题,那么线程池应运而生。
线程池的作用
我们来看一下使用线程池的好处:
线程池类继承结构
我们先看一张有关线程池的类继承结构图:
ThreadPoolExecutor
构造函数
这个类中有很多构造函数,我们找一个参数最多的构造函数来看一下。
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
这个参数表示的是核型线程数,当一个请求进来时当前线程池中的线程个数小于核心线程数,可以直接通过ThreadFactory
创建线程池;如果已经大于核心线程数时,则将任务放入到workQueue
(任务队列)中。
maximumPoolSize
这个参数表示线程池中可以创建的最大线程数。当线程池中的线程数等于corePoolSize
并且workQueue
(任务队列)已满,这时就要看当前线程数是否大于maximumPoolSize
,如果小于则会创建线程去执行任务,否则会时候“饱和策略”去拒绝这个任务请求。对于超过corePoolSize的线程称之为“idle Thread
”,这部分线程会有一个最大的空闲存活时间,如果超过这个空闲存活时间还没有任务被分配,则会将这些线程进行回收。
keepAliveTime 和 unit
这两个参数就是用来控制“idle Thread
”(空闲线程)的空闲存活时间,u nit
表示时间单位,当超过这个时间时将会被回收。在ThreadPoolExecutor
中有一个非常重要参数private volatile boolean allowCoreThreadTimeOut
,这个参数表示当核心线程超过最大空闲时间还没被分配任务是是否回收,默认返回false
,表示不会被回收。如果将这个参数设置成true
的话,当核心线程超过最大空闲时间时将会被回收。
workQueue
阻塞队列,当线程数超过corePoolSize
的部分任务会被放入到这个队列中等待执行。阻塞队列也会被分成有界和无界,当我们制定这个队列的capacity
时,就是一个有界阻塞队列,反之就是一个无界阻塞队列。当该队列为无界阻塞队列时,会有大量的任务被存入,从而导致内存溢出系统崩溃。
threadFactory
这是一个线程工厂,被用来为线程池创建线程。当我们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()
创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY
。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。
handler
饱和策略,当线程池达到饱和状态时拒绝多余的任务。ThreadPoolExecutor
中有三种饱和策略,AbortPolicy
:执行策略时抛出RejectedExecutionException
异常。CallerRunsPolicy
:不在线程池中运行任务,在调用者的线程中运行任务。DiscardOldestPolicy
:将队列中等待最久的直从队列头部移除,将新的任务加入到队列尾部。DiscardPolicy
:直接丢弃任务。
执行方法
线程池中有两种执行方法,分别是submit()
和execute()
,下面我们通过源码看一下两者的区别。
execute()
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); //1
if (workerCountOf(c) < corePoolSize) { //2
if (addWorker(command, true)) //3
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //4
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //5
reject(command);
else if (workerCountOf(recheck) == 0) //6
addWorker(null, false);
}
else if (!addWorker(command, false)) //7
reject(command);
}
在上面有几处注释,我们看一下。
submit()
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
submit(...)
方法其实是AbstractExecutorService
中的方法,ThreadPoolExecutor
继承于它,这里的submit``方法又会调用
ThreadPoolExecutor的
execute(...)```方法。
线程的池的运行过程
从上面的执行方法我们能获得下面关于线程池运行过程的图。
线程池的运行过程主要分一下几个步骤:
今年金九银十我花一个月的时间收录整理了一套知识体系,如果有想法深入的系统化的去学习的,可以点击传送门,我会把我收录整理的资料都送给大家,帮助大家更快的进阶。