什么是线程池?在Java中,如果每个请求到达后就创建一个线程,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。
如果在一个JVM里创建太多的线程,可能会是系统由于过度消耗内存或者CPU时间片切换过度而导致系统资源不足。
为了解决这个问题,就有了线程池的概念。线程池的核心逻辑是提前创建好若干个线程,放在一个容器中。如果没有任务需要处理,则将任务直接分配给线程池的线程来执行就行,任务处理完以后,这个不会被销毁,而是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建大量线程增加开销。
线程池的优势
合理的使用线程池,可以带来如下好处:
1,降低创建线程和销毁线程的性能开销。
2,提高响应速度,当有新任务需要执行时,不需要等待线程创建就可以立马执行。
3,合理的设置线程池的大小,可以避免因为线程数量超过硬件资源瓶颈带来的问题。
常用线程池
JDK
自带的线程池(Executors工厂类或者ThreadPoolExector)
Spring封装的线程池
JDK
中提供的线程池API
的使用
既然是搞Java开发,线程池这个名词肯定不陌生了,或多或少有点点了解,但是实话实说,能真正的在工作中,尤其是写业务代码中使用过线程池的肯定不多,但是绝对不影响咱们学习线程池,说不定那天你在工作中就用了呢,再说面试中也是常问题目之一,既然这样,那么咱们还是很有必要对线程的原理要有着深层次的理解。
要了解一个技术,我们仍然是从使用开始,JDK为我们提供了几种不同线程池实现。我们先通过一个简单的案例来引入线程池的基本使用。
运行结果:
JDK
中自带的线程池API
为了方便于大家对于线程池的使用,在Executors里面提供了几个线程池的工厂方法,这样对于很多新手就不需要了解太多关于线程池的知识了,只需要直接使用Executors的工厂方法,就可以创建线程池了。下面介绍常用几个方式:
newFixedThreadPool
该方法返回一个固定数量的线程池,线程数量不变,当有一个任务提交时,如线程池中存在空闲线程,则立即执行任务,如果没有空闲线程,则会被暂缓存放在一个任务队列中(容器中),等待由空闲线程去执行。
核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。另外 keepAliveTime
为 0,也就是超出核心线程数量以外的线程空余存活时间,
而这里选用的阻塞队列是LinkedBlockingQueue
,使用的是默认容量Integer.MAX_VALUE
,相当于没有上限。
这个线程池执行任务的流程如下:
线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
线程数等于核心线程数后,将任务加入阻塞队列
由于队列容量非常大,可以一直添加
执行完任务的线程反复去队列中取任务执行
用途
newFixedThreadPool
用于负载比较大的服务器,为了资源的合理利用,需要限制当前线程数量
newSingleThreadExecutor
创建一个线程的线程池(单个线程的线程池)。如果存在空闲则执行任务,否则将任务暂缓存放在任务队列中。
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程; 并且没有核心线程,非核心线程数无上限,但是每个空闲的时间只有 60 秒,超过后就会被回收。
它的执行流程如下:
没有核心线程,直接向 SynchronousQueue 中提交任务
如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收
newScheduledThreadPool
创建一个可以指定线程的数量大小的线程池,同时这个线程池还带有延迟和周期性执行任务的功能,类似于定时器,很多框架中喜欢该方法来实现心跳检测。
ThreadpoolExecutor
上面提到的四种线程池的构建,都是基于 ThreadpoolExecutor
来构建的,小伙伴们打起精神来了,接下来将一起了解一下面试官最喜欢问到的一道面试题“请简单说下你知道的线程池和ThreadPoolThread
有哪些构造参数”:
Executors.newFixedThreadPool(int nThreads);
源码部分:
ThreadpoolExecutor
有多个重载的构造方法,我们可以基于它最完整的构造方法来分析先来解释一下每个参数的作用,稍后我们在分析源码的过程中再来详细了解参数的意义。
线程池参数含义解释
int corePoolSize, //核心线程数量
int maximumPoolSize, //最大线程数
long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
TimeUnit unit, //存活时间单位
BlockingQueue<Runnable> workQueue, //保存执行任务的队列
ThreadFactory threadFactory,//创建新线程使用的工厂
RejectedExecutionHandler handler //当任务无法执行的时候的处理方式</pre>
这个地方有很多小伙伴一起聊过,线程池初始化以后做了什么事情,线程池初始化时是没有创建线程的,线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。