线程和线程池
个人博客:kana.chat:90
线程:线程即资源调度的最小单位,在linux中即为轻量级进程LWP(light weight process)
线程可分大致两种:
用户级线程(ULT)由用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度、管理线程的函数控制线程,不需要用户态/内核态切换,线程阻塞进程(包括它的所有线程)
内核级线程(KLT) 由系统内核管理线程,内核保存线程状态和上下文信息,线程阻塞不会引起进程阻塞,多线程在多处理器的系统上并行运行,线程的创建、管理、调度由内核管控,效率慢与用户级线程,快于进程。
对于绝大多数java虚拟机,使用的是内核级线程。
针对于大量的线程的创建和切换,使用线程池更利于线程的维护。线程池有利于线程的重用而执行多个任务,其本身是线程的缓存,负责对线程进行统一分配、调优和监控。
在单个任务处理时间短且有多个任务的时候,使用线程池效率较高。而阿里巴巴开发手册中也写到,使用线程池前必须自定义线程池。
线程池顶层接口Executor:
线程池执行原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YoXjluGW-1602049148204)(https://kana-bucket.oss-cn-beijing.aliyuncs.com/%E5%9B%BE%E7%89%87_1598251616469.png)]
由上图也可看出,线程池使用了阻塞队列(有界||无界),任意时刻只有一个线程能够进出队列,而且当队列满或者空的时候,也会被阻塞(等待出队/入队)。
每次线程池只会执行固定的任务(每个任务必须实现runnable接口)。
线程池初始化的时候内部没有线程,当任务提交会去创建核心线程,当核心线程已满会将新提交的任务放入阻塞队列(BlockingQueue,这一块不同线程池使用不同的阻塞队列,可以是有界或者无界队列)中。
若提交新任务时阻塞队列已满且当前核心线程仍然忙碌时,会创建临时线程去执行新提交的任务,临时线程若空闲时间超过指定的时间(即一段时间内现有任务不足以让临时线程去工作),临时线程就会被取消(本质上是随机取消)。如果临时线程数量也已满,线程池内部会提供自带的拒绝策略(使用者也可以直接扩充)。
如上图:AbortPolicy(默认使用的拒绝策略)抛出异常,DiscardPolicy丢弃阻塞队列最旧的任务,DiscardPolicy什么也不做,CallerRunsPolicy: 如果线程池没有SHUTDOWN的话,直接执行任务。
自定义线程池时需要设置核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、临时线程最大空闲时间(keepAliveTime)、阻塞队列、拒绝策略等。线程池不保证任务的顺序,其一因为临时线程的存在,其二因为线程的并发性导致cpu的任务调度不一定是先来先服务。
线程池的五种状态:
Running:能接受新任务,并且处理已经添加的任务
Shutdown:不接受新任务但可以处理已经添加的任务
Stop:不接受新任务也不处理已经添加的任务,并且中断正在处理的任务
Tidying:所有的任务已经终止,ctl记录的“任务数量”为0
注:ctl负责记录线程池的运行状态与活动线程数量
Terminated:线程池彻底终止,转入terminated状态
每次线程工作时都会将任务包装成一个Worker(一个线程池的内部类,继承了AQS并且实现了runnable接口),调用addWorker方法先判断线程的状态,之后创建一个Worker。Worker中会将每个任务放入firstTask,并且使用getThreadFactory.newThread(this)创建新的核心线程,最终Worker会执行task的run方法。
线程池如何保证状态的切换
java中使用高3位记录线程生命状态,低29为记录当前工作线程数(上图位运算本质是对应的数的二进制左移29位)。
总结:
-
线程池倾向于使用核心线程来处理任务,从任务的添加策略可以看出,先考虑创建核心线程处理,再考虑放到阻塞队列,再考虑创建非核心线程处理。以上都不行,则使用任务拒绝策略
-
通过向阻塞队列取任务的不同操作,能确保线程的存活,take保证核心线程不死,poll保证非核心线程存活等待一定时间
-
线程池不区分核心线程和非核心线程,线程池是期望达到corePoolSize的并发状态,并允许在不得已情况下超载,达到corePoolSize ~ maximumPoolSize 的并发状态
-
线程池状态和线程数量用ctl表示,高三位为数量,低29位为当前线程池数量
-
线程池对状态的检测非常苛刻,几乎在所有稍微耗时或影响下一步操作正确性的代码前都校验ctl
常见的线程池选择:
CachedThreadPool:适合异步任务多,但周期短的场景
FixedThreadPool:适合有一定异步任务,周期较长的场景,能达到有效的并发状态
SingleThreadExecutor:适合任务串行的场景
ScheduledThreadPool:适合周期性执行任务的场景
最后再放一张网图来清晰一下步骤: