为了提高性能和充分利用系统资源,通常会选择使用多线程技术。然而线程的启动、销毁成本是比较高的,线程之间的切换也会消耗大量的JVM资源,所以线程池的出现是为了更好管理和调度线程的一种方式,和连接池和对象池的初衷一样,在有空闲资源的前提下,让现有资源充分重复利用,避免不必要的开销。
线程池的好处:
降低资源消耗
提高响应速度
增强线程的管理
自带线程池概述
JDK已经实现了4个经典线程池
Executors.newCacheThreadPool()
可缓存线程池——先检查线程池中是否有已经创建且空闲的线程,如果有直接使用,否则创建一个新的线程加入线程池并启动
Worker线程的数据量没有限制——Interger.MAX_VALUE
Worker默认的生命周期为1min,即当线程池中线程空闲时间超过1min,此线程会被自动销毁,当有新的任务进来时重新建立线程
1 | /** |
Executors.newFixedThreadPool()
可控Worker数量的线程池,此线程池中有一个
无界队列
,当提交的Worker数量大于设定值n时,提交的线程不会立刻启动,而是放到队列中;当n中某些线程执行结束,队列中的线程才得以执行从源码中得知,
newFixedThreadPool
用的是LinkedBlockingQueue
,此队列是一个基于链表结构
的阻塞队列,此队列按FIFO
原则存储元素在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源,即始终会保留设定值n个线程在线程池中
1 | /** |
Executors.newScheduledThreadPool()
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行
看其源码构造方法中,有一个延迟队列
DelayedWorkQueue
JDK中定义:Delayed元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS)方法返回一个小于等于0的值时,将发生到期。即使无法使用take或poll移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size方法同时返回到期和未到期元素的计数。此队列不允许使用null元素。
1 | /** |
Executors.newSingleThreadExecutor()
单线程池,即提交到此线程池的线程只能用一个Worker线程去执行;保证所有任务按照指定顺序(FIFO, LIFO)执行;多应用于并发操作某类只能由一个线程处理的任务,例如多线程写多个不同的文件,每个线程对应于一个文件,但对于一个文件来说同一时刻只能由一个线程顺序去写。
1 | /** |
自定义线程池ThreadPoolExecutor
1 | ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue,handler) |
【Params】:
corePoolSize(线程池的基本大小-可运行Worker线程数量)
:当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程runnableTaskQueue(任务队列)
:当线程超过corePoolSize设定的个数,会将线程先放到阻塞队列中不立刻执行
可以选择以下几个阻塞队列:
ArrayBlockingQueue:一个基于数组的有界阻塞队列,此队列按
FIFO
原则LinkedBlockingQueue:一个基于链表的阻塞队列,此队列按
FIFO
原则,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于
LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列
PriorityBlockingQueue:一个具有优先级的无限阻塞队列
maximumPoolSize(线程池最大大小)
:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果ThreadFactory
:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字RejectedExecutionHandler(饱和策略)
:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。
这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略:
AbortPolicy:直接抛出异常
CallerRunsPolicy:只用调用者所在线程来运行任务
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
DiscardPolicy:不处理,丢弃掉
当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
```
keepAliveTime(线程活动保持时间)
:线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率TimeUnit(线程活动保持时间的单位)
:可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)