一、使用线程池的目的
在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程。使用线程池管理线程主要有以下好处:
1、降低资源消耗:通过复用已经存在的线程和降低线程关闭的次数来尽可能降低系统损耗;
2、提供系统的响应速度:通过复用线程,省去创建线程的过程,因此整体上提升系统的响应速度;
3、提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会降低系统稳定性,因此,需要使用线程池来管理线程。
二、线程池组成
一般一个简单线程至少有以下部分组成.
1、线程管理器(ThreadPoolManager):用于创建并管理线程池;
2、工作线程(workThread):线程池中线程;
3、任务接口(Task):每个任务都必须实现的接口,以供工作线程调度任务的执行;
4、任务队列:用于存放没有处理的任务,提供一种缓冲机制。
线程池管理器至少有以下列功能:创建线程池,销毁线程池,添加新任务,创建线程池。具体如下图:
三、线程池组成
当一个并发任务提交线程池,线程池分配线程去执行任务过程如图所示:
从图可以看出,线程池执行所提交的任务过程主要有这样几个阶段:
1、先判断线程池中核心线程池所有的线程是否都在执行任务,如果不是,创建一个线程执行刚提交的任务,否则,核心线程池中的所有线程都在执行任务,则进入第2步;
2、判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞队列中;都在进入第3步;
3、判断线程池中所有的线程是否都在执行任务,如果没有,则创建一个新线程执行任务,否则交给饱和策略进行处理。
四、线程池的创建
创建线程池主要是ThreadPoolExecutor类来完成,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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
下面对参数进行说明:
1、corePoolSize:表示线程池的大小,当提交一个任务时,如果当强核心池的线程个数没有达到corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程,如果当前核心线程池的线程个数已经到达CorePoolSize,则不再创建线程,如果调用了prestartCoreThread()
或者 prestartAllCoreThreads(),
线程池创建的时候所有核心线程都会被创建并且启动。
2、maximumPoolSize:表示线程池能创建线程的最大个数。如果当阻塞队列已满时,并且当前线程池个数没有超过maxmumPoolSize的话,就会创建新的线程来执行任务。
3、keepAliveTime:空闲线程存活时间,如过当前线程池个数已经超过CorePoolSize,并且线程空闲时间超过了KeepAliveTime的话,就会将空闲线程销毁,这样尽可能降低系统资源消耗。
4、unit:时间单位,为keepAliveTime指定时间单位。
5、workQueue:阻塞队列,用于保存任务的阻塞队列,可以使用ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,priorityBlockingQueue;
6、ThreadFactory:创建线程的工程类,可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字。如果出现问题,也方便查找问题原因。
7、handler:饱和策略,当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况,采用的策略有这几种:
- AbortPolicy:直接拒绝所提交的任务,并抛出RejectedExecutionException异常;
- CallerRunsPolicy:只用调用者所在线程来执行任务;
- DiscardPolicy:不处理直接丢掉任务;
- DiscardOldestPolicy:丢掉阻塞队列中存放时间最久的任务,执行当前任务;
ThreadPoolExecutor的execute方法执行逻辑请见注释。下图为ThreadPoolExecutor的execute方法的执行示意图:
Execute方法执行逻辑有这样几种情况:
1、如果当前运行的线程少与corePoolSize,则会创建新的线程来执行新的任务;
2、如果运行的线程个数等于或者大于corePoolSize,则会将提交的任务存放到阻塞队列workQueue中;
3、如果当前workQueue队列已满的话,则会创建新的线程来执行任务;
4、如果线程个数已经超过了maximunPoolSize,则会使用饱和策略RejectedExecutionHandler来进行处理;
需要注意的是,线程池的设计思想就是使用了核心线程池CorePoolSize,阻塞队列workQueue和线程池maxmumPoolSize,这样的缓存策略来处理任务,这种设计思想在许多框架中都会使用。
五、线程池的关闭
关闭线程池,可以通过shutdown和shutdownNow这两个方法。他们的原理都是遍历线程池中所有的线程,然后依次中断线程。二者区别如下:
1、shutdownNow 首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行的线程。并返回等待执行的任务列表;
2、shutdown只是将线程池的状态设置为shutdown状态,然后中断所有没有正在执行线程。
可以看出shutdown方法会将正在执行的任务继续执行完,而shutdownNow会直接中断正在执行的任务。调用了这两个方法的任意一个,isshutdwon方法都会返回true,当所有线程都关闭成功,才表示线程池关闭。这是调用的isTerminated方法才会返回true。
六、如何合理配置线程池参数
要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:
1、任务的性质:CPU密集性任务,IO密集型任务和混合型任务;
2、任务的优先级:高、低、中
3、任务执行的时间:长、中、短
4、任务的依赖性:是否依赖其他系统资源,如数据库连接等;