1:什么是线程池:
顾名思义,线程池就是一个装有线程的池子,我们把需要执行的多线程交给线程池来处理,通过维护一定数量的线程池来达到多个线程的复用;
另一方面,假设我们创建线程的时间为T1,线程的工作时间为T2,线程的销毁时间为T3,当T1+T3远大于T2时,就需要采用线程池来提高性能。线程池技术是关注于解决如何缩短T1和T3时间的技术,通过调整线程创建和线程销毁的时间,以及减少重复创建线程的数目,来提升服务器程序处理的速度。
2:为什么要用线程池:
当我们创建使用线程的时候,会使用Thread的start()方法,当线程数量较少时,系统可以正常应对。但是当线程数量较大时,会耗尽CPU和内存资源,导致GC的频繁收集和停顿。每一次创建线程和销毁线程都要耗费系统资源,如果我们为每一个任务都单独开一个线程,无疑会造成很大的性能问题。因此,线程池中的线程复用可以极大的节省系统资源,当线程一段时间不再有任务处理时,他会自动销毁,而不会常驻内存;
3:线程池的原理:
java线程池相当于一个线程集合workerSet和一个阻塞队列workQueue的组合体。当用户向线程池提交一个任务(也就是线程)时,线程池先将任务放进阻塞队列中。线程集合中的线程会不断从阻塞队列中获取任务来执行,当阻塞队列中没有任务时,线程集合中的线程就会阻塞,直到有新的任务进入到阻塞队列中,再取出来继续执行。
4:线程池的核心类:
在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,其中主要的参数有:
(1)corePoolSize: 规定线程池有几个线程(worker)在运行。
(2)maximumPoolSize: 当workQueue满了,不能添加任务的时候,这个参数才会生效。规定线程池最多只能有多少个线程(worker)在执行。
(3)keepAliveTime: 超出corePoolSize大小的那些线程的生存时间,这些线程如果长时间没有执行任务并且超过了keepAliveTime设定的时间,就会消亡。
(4)unit: 生存时间对应的单位。
(5)workQueue: 存放任务的队列。
(6)threadFactory:创建线程的工厂,一般用默认即可。
(7)handler: 当workQueue已经满了,并且线程池线程数已经达到 maximumPoolSize,将执行拒绝策略,默认抛出异常。
5:线程池的工作流程:
用户通过submit提交一个任务。线程池会执行如下流程:
(1)判断当前运行的worker数量是否超过corePoolSize,如果不超过corePoolSize。就创建一个worker直接执行该任务。—— 线程池最开始是没有worker在运行的
(2)如果正在运行的worker数量超过或者等于corePoolSize,那么就将该任务加入到workQueue队列中去。
(3)如果workQueue队列满了,也就是offer方法返回false的话,就检查当前运行的worker数量是否小于maximumPoolSize,如果小于就创建一个worker直接执行该任务。
(4)如果当前运行的worker数量已经大于等于maximumPoolSize,那么就执行RejectedExecutionHandler来拒绝这个任务的提交。
6:常见的四种线程池:
(1)newFixedThreadPool
固定线程池,核心线程数和最大线程数固定相等,每提交一个任务就创建一个线程,直到达到线程池的最大数量。再之后进入的任务全部丢到阻塞队列直到前面的任务完成才继续执行,阻塞队列满了就执行默认的拒绝策略。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
(2)newCachedThreadPool(推荐使用)
可缓存线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
(3)newSingleThreadExecutor
单线程线程池,核心线程数和最大线程数均为1,空闲线程存活0毫秒,同样无意义。每次只执行一个线程,多余的先存储到工作队列,一个一个执行,保证了线程的顺序执行。
(4)newScheduledThreadPool
可调度线程池,即按一定的周期执行任务,即定时任务,对ThreadPoolExecutor进行了包装而已。
7:阿里巴巴Java开发手册,明确指出不允许使用Executors静态工厂构建线程池:
原因如下:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1:FixedThreadPool 和 SingleThreadPool:
允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(内存耗尽)
2:CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
正确的创建线程池的方法:
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。
8:提交一个线程:
executor.submit(xxRunnble);
executor.execute(xxRunnble);
submit和execute分别有什么区别呢?
(1)execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能会好很多。
(2)submit返回一个Future对象,如果想知道线程结果就使用submit提交,而且它能在主线程中通过Future的get方法捕获线程中的异常。
9:关闭线程池:
调用shutdown()方法;