线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
为什么要用线程池?
Java 中创建一个线程,需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。
使用线程池就能很好地避免频繁创建和销毁。
为了减少在创建和销毁线程上所花的时间和对系统资源的消耗,解决资源不足的问题。
如果不适用线程池,有可能造成系统创建大量同类线程而消耗内存挥着”过度切换“问题。
四种线程池
- SingleThreadPool:单个线程线程池,core = max =1,拿一个任务用一个线程
- FixedThreadPool:指定大小的固定大小线程池,core=max, 都不能回收,无界队列
- CachedThreadPool:core是0,所有的线程都可回收,无界队列
- ScheduledThreadPool : 定时任务专用的线程池
问题
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Interger.MAX_VALUE,可能会堆积大量请求,造成OOM;
- CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,造成OOM。
线程池参数?
- corePoolSize:核心线程数量,定义了最小可以同时运行的线程数量,没有任务也不会被回收。
- maximumPoolSize:当线程数量大于核心线程数,且队列中存放的任务达到队列容量的时候,继续创建线程。
- keepAliveTime:超过corePoolSize之后”临时线程“的存活时间。
- timeUnit:单位
- workQueue:当新任务来的时候,保存未执行的任务。大于corePoolSize时进入阻塞队列等待。
- threadFactory:线程工厂,用来创建线程,默认就行
- RejectdExecutionHandler handler:拒绝策略,线程数量达到maximumPoolSize大小,且workQueue也塞满的情况下,线程池调用handler拒绝策略来处理请求。
线程池运行流程:
1.线程池创建core数量的核心线程准备接受任务
2.core满了之后,任务进入阻塞队列
3.阻塞队列慢之后,开新线程执行,开到max
4.到max之后,max - core 数量的空闲线程会在keepAliveTime指定时间后销毁,最后保持到core数量的线程
5.max也满了,还有新任务进来,就开始执行线程池的拒绝策略
举例:
一个线程池,core5 , max 20 queue 50, 100并发:
首先5个直接执行。接下来50个进入队列排队,再多开15个继续执行,现在70个被安排了,剩余30个任务进入到默认的拒绝策略处理。
线程池拒绝策略:
-
AbortPolicy:默认拒绝策略,直接抛异常
-
DiscardPolicy:直接抛弃不处理。
-
DiscardOldestPolicy:丢弃队列中最老的任务。
-
CallerRunsPolicy:将任务分配给当前执行execute方法的线程来执行
自定义拒绝策略:实现
RejectedExecutionHandler
接口,可以实现友好的拒绝策略。包括但不限于:将数据存入数据库,用日志处理等。
JDK 的并发工具包里还有一个静态线程池工厂类 Executors,可以方便地创建线程池,但是由于 Executors 创建的线程池内部很多地方用到了无界任务队列,在高并发场景下,无界任务队列会接收过多的任务对象,导致 JVM 抛出OutOfMemoryError,整个 JVM 服务崩溃,影响严重。所以很多公司已经不建议使用 Executors 去创建线程。
线程池包含哪些状态?
线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
见 ThreadPoolExecutor 源码
// runState is stored in the high-order bits
private static final int RUNNING = -1 <<COUNT_BITS;
private static final int SHUTDOWN = 0 <<COUNT_BITS;
private static final int STOP = 1 <<COUNT_BITS;
private static final int TIDYING = 2 <<COUNT_BITS;
private static final int TERMINATED = 3 <<COUNT_BITS;
-
RUNNING:
线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。 -
SHUTDOWN:
不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。 -
STOP:
不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。 -
TIDYING:
SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。
线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。
线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。 -
TERMINATED:
线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。
线程池关闭方法
1.shutdown()
- 有序关闭,已提交任务继续执行
- 不接受新任务
2、shutdownNow()
- 尝试停止所有正在执行的任务
- 停止等待执行的任务,并返回等待执行的任务列表
3、awaitTermination(long timeout, TimeUnit unit)
- 收到关闭请求后,所有任务执行完成、超时、线程被打断,阻塞直到三种情况任意一种发生
- 参数可以设置超时时间与超时单位
- 线程池关闭返回 true;超过设置时间未关闭,返回 false
总结
- 调用 shutdown() 和 shutdownNow() 方法关闭线程池,线程池都无法接收新的任务
- shutdown() 方法会继续执行正在执行未完成的任务;shutdownNow() 方法会尝试停止所有正在执行的任务
- shutdown() 方法没有返回值;shutdownNow() 方法返回等待执行的任务列表
- awaitTermination(long timeout, TimeUnit unit) 方法可以获取线程池是否已经关闭,需要配合 shutdown() 使用
- shutdownNow() 不一定能够立马结束线程池,该方法会尝试停止所有正在执行的任务,通过调用 Thread.interrupt() 方法来实现的,如果线程中没有 sleep() 、wait()、Condition、定时锁等应用,interrupt() 方法是无法中断当前的线程的。