JAVA线程池

1 线程池概述

1.1 为什么使用线程池

对于线程的创建和切换代价都是比较大的,为了能够更好的使用线程,所以就产生了线程池的概念。Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。其带来的好处如下:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
  • 提高线程的可管理性:线程是稀缺资源,要合理利用分配,通过线程池可以进行统一分配、调优和监控

1.2 线程池的状态

线程池存在五种状态:RUNNING、 SHUTDOWN,、STOP、TIDYING、TERMINATED

  • RUNNING:处于RUNNING状态的线程池能够接受新任务,以及对新添加的任务进行处理
  • SHUTDOWN:处于SHUTDOWN状态的线程池不可以接受新任务,但是可以对已添加的任务进行处理
  • STOP:处于STOP状态的线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
  • TIDYING:当所有的任务已终止,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若想在线程池变为TIDYING时,进行相应的处理,可以通过重载terminated()函数来实现
  • TERMINATED:线程池彻底终止的状态

在这里插入图片描述

2 线程池底层原理

  1. 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务

  2. 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;

  3. 阻塞队列已满,则会尝试创建新的线程去执行这个任务

  4. 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理

  5. 如果线程池中的线程数量大于corePoolSize时,且某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止

  6. 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程
    在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
    prestartCoreThread():初始化一个核心线程;
    prestartAllCoreThreads():初始化所有核心线程

  7. workQueue的类型为BlockingQueue,通常可以取下面三种类型:

    • ArrayBlockingQueue:以数组作为数据存储容器,创建时必须指定大小
    • LinkedBlockingQueue:基于链表的无界阻塞队列(也可以指定队列长度),如果没有指定其容量大小,则默认为 Integer.MAX_VALUE
    • synchronousQueue:一个无缓冲不存储元素的阻塞队列
  8. ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
    • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

在这里插入图片描述

在这里插入图片描述

3 线程池使用

3.1 自定义线程池

利用ThreadPoolExecutor自定义线程池

在这里插入图片描述

参数分析:

corePoolSize:
​核心线程数(线程池基本大小),在没有任务需要执行的时候的线程池大小
当提交一个任务时,线程池创建一个新线程执行任务,直到线程数等于该参数。 如果当前线程数为该参数,后续提交的任务被保存到阻塞队列中,等待被执行

maximumPoolSize:
​线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值
如果当前阻塞队列满了,且继续提交任务,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务

keepAliveTime:
线程空闲时的存活时间,即当线程池中有线程没有任务执行的时候,该线程能继续存活的时间
默认情况下,该参数只在线程数大于corePoolSize时才有用

workQueue:
​其必须是BolckingQueue有界阻塞队列,用于实现线程池的阻塞功能
当线程池中的线程数超过它的corePoolSize时,线程会进入阻塞队列进行阻塞等待

threadFactory
用于设置创建线程的工厂
ThreadFactory的作用就是提供创建线程的功能的线程工厂。他是通过newThread()方法提供创建线程的功能,newThread()方法创建的线程都是“非守护线程”而且“线程优先级都是默认优先级”

handler:
线程池拒绝策略。当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,则必须采取一种策略处理该任务:

  • AbortPolicy:默认策略,直接抛出异常
  • CallerRunsPolicy:用调用者所在的线程执行任务
  • DiscardOldestPolicy:舍弃队列中等待最久的任务,把当前任务添加到队列首位并尝试提交
  • DiscardPolicy:直接丢弃任务

3.2 预定义线程池

阿里开发规范中均不建议使用

3.2.1 FixedThreadPool

在这里插入图片描述
创建使用固定线程数的线程池
适用于为了满足资源管理而需要限制当前线程数量的场景;同时也适用于负载较重的服务器
在这里插入图片描述

3.2.2 SingleThreadExecutor

在这里插入图片描述

只会使用单个工作线程来执行一个无边界的队列
适用于保证顺序地执行各个任务,并且在任意时间点,不会有多个线程存在活动的应用场景
在这里插入图片描述
因为LinkedBlockingQueue是长度为Integer.MAX_VALUE的队列,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常。同时因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程

3.2.3 CachedThreadPool

在这里插入图片描述
其是一个大小无界的线程池,会根据需要创建新线程
适用于执行很多的短期异步任务的小程序或者是负载较轻的服务器
在这里插入图片描述
CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但它的maximumPoolSize是无界的。这意味着,如果主线程提交任务的速度高于线程处理任务的速度时,
会不断创建新线程。极端情况下,会因为创建过多线程而耗尽 CPU 和内存资源

3.2.4 ScheduledThreadPoolExecutor

在这里插入图片描述
继承ThreadPoolExecutor且实现了ScheduledExecutorService接口,它就相当于提供了“延迟”和“周期执行”功能的ThreadPoolExecutor。它可另行安排在给定的延迟后运行命令,或者定期执行命令
适用于为了满足资源管理的需求而需要限制后台线程数量的场景同时可以保证多任务的顺序执行

DelayedWorkQueue为ScheduledThreadPoolExecutor中的内部类,类似于延时队列和优先级队列。在执行定时任务的时候,每个任务的执行时间都不同,所以DelayedWorkQueue的工作就是按照执行时间的升序来排列,执行时间距离当前时间越近的任务在队列的前面,这样就可以保证每次出队的任务都是当前队列中执行时间最靠前的

如果在任务的执行中遇到异常,后续执行被取消

3.2.5 WorkStealingPool

在这里插入图片描述

其是JDK1.8中新增的线程池,利用所有运行的CPU来创建一个工作窃取的线程池
适用于非常耗时的操作

直接基于CPU使用,会影响CPU的使用频率

4 线程池常见问题分析

4.1 使用无界阻塞队列会发生什么?

当任务提交的速度大于线程处理任务的速度时,任务会不断积压到队列中,而队列又是无界的,所以队列会越来越大,必然会导致内存占用飙升,当超过一定值,还可能导致 OOM(内存溢出)

4.2 队列满了会发生什么?

有界队列,有效避免了内存溢出
检查线程数是否达到maximumPoolSize:没有达到,会创建新的线程来执行任务;达到,会执行拒绝策略
如果maximumPoolSize没有指定大小(默认 Integer.MAX_VALUE),线程池无限制的创建额外的线程,而每个线程都会占用一定的内存资源,会导致内存资源耗尽,系统 崩溃;即便内存没有崩溃,也会导致 CPU 负载过高,导致机器崩溃

4.3 线上机器突然宕机,队列中的请求会怎么办?

必然导致线程池里积压的任务会丢失

解决方案:

队列中的任务在提交到线程池里前,先在数据库里插入这个任务信息,并用一个状态字段标识该任务的处理进度:未提交、已提交、已完成等
提交成功之后,更新他的状态是已提交状态
系统重启后,后期去查询数据库里未提交和已提交的任务,把任务信息读取出来,重新提交到线程池,继续执行

宕机发生在更新状态的时候,数据库中的记录状态没有更新
需要对接口做幂等处理,防止重复提交

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值