为什么使用线程池?
Java线程
线程是调度CPU的最小单元,也叫轻量级进程LWP(Light Weight Process)
线程分类
用户级线程(User-Level Thread,简称:ULT)
用户程序实现,不依赖操作系统核心,应用提供创建\同步\调度和管理线程的函数来控制用户进程.不需要用户态\内核态切换,速度快,内核对ULT无感知,线程阻塞则进程(包括它的所有线程)阻塞
内核级线程(Kernel-level Thread,简称:KLT)
系统内核管理线程(KLT),内核保持线程的状态和上下文信息,线程阻塞不会引起程序阻塞.在多处理器操作系统上,多线程在多处理器上并行运行.线程的创建\调度和管理由内核完成,效率比ULT要慢,比进程操作快
JVM虚拟机使用的是KLT
划分的原因:保证系统内核的安全,避免用户直接操作内核
普通线程创建执行过程
多线程下,会根据时间片算法来分配线程的执行,例如现有两个线程,线程一的时间片用完了,下一个时间片是线程二要执行,线程一的上下文信息(指令\程序缓存\中间数据)就要保存到内核空间的TSS任务状态段,下次拿到时间片的时候再从TSS任务状态段中重载上下文继续执行,而且java是依赖于内核级线程,会在用户态和内核态进行切换
但这样会产生一个问题,在高并发的情况下,不断地创建线程和用户态与内核态切换,会消耗大量的资源,导致系统崩溃
而线程池就是来解决这个问题的,复用线程,减少线程的创建
线程池的意义
线程是稀缺资源,它的创建与销毁是比较重且耗资源的操作.而Java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为避免资源过度消耗需要设法重用线程执行多个任务.线程池就是一个线程缓存,负责对线程进行统一分配\调优与监控
什么时候使用线程池?
- 重用存在的线程,减少线程创建,消亡的开销,提高性能
- 提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性,线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配\调优和监控
实现方式
Executors线程池创建工具(不推荐)
这个类提供了5种创建线程池的方法,它只是jdk提供的线程池的工具类,封装了线程池的创建方式,本质它还是使用后面讲的那种方式进行创建的
- newFixedThreadPool 创建固定线程数量的线程池
- newSingleThreadExecuror 创建只有一个线程的线程池
- newCachedThreadPool 创建n个线程的线程池(可缓存)
- …
ThreadPoolExecutor
这里主要使用ThreadPoolExecutor这个类创建线程池
构造函数主要参数
- corePoolSize: 核心线程数量
- maximumPoolSize: 最大临时线程数(含核心线程)
- keepAliveTime: 临时线程不使用后销毁延时
- unit: 延时时间单位
- workQueue(线程安全): 核心线程没有及时处理的东西先放到这个阻塞队列,如果满了就会开启最大线程,使用最大线程中创建的临时线程,
在任意时刻,不管并发有多高,永远只有一个线程能够进行队列的入队或者出队操作 - threadFactory: 线程工厂
- rejectedExecutionHandler: 拒绝策略,即核心线程,阻塞队列,最大临时线程都处于负载的情况下,如果还有任务进来就会触发拒绝策略
1.AbortPolicy: 抛弃任务,并且会抛出异常(默认)
2.DiscardPolicy: 既不处理任务,也不抛出异常
3.DiscardOldestPolicy: 丢弃队列最前面的任务
4.CallerRunsPolicy: 调用者回退策略,将任务给调用者处理工作原理图
线程池生命状态
Running: 能接收新任务,以及处理已经添加的任务
Shutdown: 不接收新任务,可以处理已经添加的任务
Stop: 不接受新任务,不处理已经添加的任务,并且中断正在处理的任务
Tidying:所有任务已经终止,ctl记录的任务数量为”0”(ctl负责记录线程池的运行状态与活动线程数)
Terminated:线程池彻底终止.则线程池转化为terminated状态分析源码
execute()方法[将任务添加到对应的地方]
worker的工作原理与线程池关闭过程
如何合理配置线程池大小
这是一个可变的,没有一个具体合理配置方案,因为这个只能根据系统环境而定,不同的配置有不同的配置方案