java多线程之线程池

本文深入解析线程状态、常用方法及线程池的工作原理。覆盖创建、就绪、运行、阻塞和关闭状态,详述start、run、wait、notify、yield、join和sleep方法。探讨线程池作用,包括任务缓存、拒绝机制、定时执行等功能。分析三种线程池:固定大小、缓存和调度线程池的特点与应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Thread

线程的状态

线程有如下几种状态:
创建——就绪——运行——阻塞——关闭
线程的几个常用方法

  • start():使用多线程时调用的方法,调用该方法后thread提交给cpu进行调度,此时线程为就绪态,需要获取cpu资源后才真正的进入运行状态
  • run():单独调用run方法只是将方法内容执行一次,没有触发多线程
  • wait():wait方法属于Object的方法,调用该方法时必须持有对象锁,也就是在同步代码块或者是同步方法中才能调用。执行该方法后,线程释放对象锁,并进入等待状态,需要调用notify或者notifyAll唤醒,唤醒后同样需要获取锁后才能继续执行,或者中断程序
  • notify():该方法可以唤醒wait中的线程,同样需要在同步代码块中执行,执行该方法后,会唤醒等待池中某个需要该对象锁的线程,这里注意的是不会马上唤醒,而是等待调用notify的线程执行同步代码完成释放锁后,wait线程才会进入就绪,等待cpu调度。notifyAll会唤醒所有线程参加锁的竞争。
  • yield():调用此方法的线程交出cpu时间片,重新进入获取cpu时间片的竞争。
  • join(Long):此方法常用于主线程等待子线程结束,例如在main中调用子线程的join方法,main一定会等待子线程结束后,main线程才会退出。
  • sleep():线程进入阻塞状态,不释放锁

线程池作用

  • 利用线程池管理并复用线程、控制最大并发数等。
  • 实现任务线程队列缓存策略和拒绝机制。
  • 实现某些与时间相关的功能,如定时执行、周期执行等。
  • 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响。

关于上述第四点,可以利用单例模式,在整个app中使用一个线程池,也可以按需求使用多个线程池

1.Executors.newFixedThreadPool

在这里插入图片描述
在这里插入图片描述

  • 该类型线程池会先创建一个指定大小的线程池,从描述可以看出,此线程池是一个定长的线程池,线程数最多不会超过指定值,当满足条件的线程被提交且可用线程数已满,该线程会加入等待队列中。如果有任意一个线程在被终止之前发生失败,会创建一个新的线程替代。

在这里插入图片描述

  • 上面的代码可以看到,核心线程数和最大线程数是一个值,实际最大线程数在此线程池中是无效的。
    线程允许的空闲时间为0。使用的是无界队列LinkedBlockingQuene,满足FIFO策略。无界队列构造器中指定的大小为Integer.MAXVALUE。这里调用ThreadPoolExecutor构造器时,源码中还指定了使用默认的拒绝策略AbortPolicy (遭到拒绝抛出异常)。

  • 结论:当提交的线程数大于核心线程数时,都会别添加到无界队列中,核心线程处理完任务后马从队列中提取新的任务,注意此过程中不会再创建新的线程,直到任务处理完毕,线程不会经过等待马上被回收。由此,固定大小的线程池存在队列大小的无限增长的可能性,适用于线程之间无影响的情况。

二、Executors.newCachedTreadPool

在这里插入图片描述

  • 缓存线程池中核心线程数为0,最大线程数为Integer.MAX_VALUE,线程空闲时间为60S,默认使用的为同步队列,拒绝策略为默认的抛异常策略。
  • 同步队列不能保持任务,所以当任务提交时,由于核心线程数为0,所以同步队列会直接提交任务,线程池创建新的线程执行任务,当任务执行完成时,并且空闲时间小于60s时,接下来的任务会首先提交到空闲线程执行。该线程池会存在无限线程的可能性。该线程池有一个特性,提交的任务一定是满足FIFO的,所以,当线程执行顺序有一定要求时,可以使用该线程池。

三、Executors.newScheduledThreadPool

在这里插入图片描述

  • 调度线程池,指定核心线程大小,最大线程数为Integer.MAX_VALUE,线程空闲时间为0纳秒,采用延时队列,默认拒绝策略抛出异常。
  • DelayedWordQueue队列是一个满足堆的排序阻塞队列,按照最先执行的时间进行排序。
  • 当线程池执行execute方法时,会调用 schedule方法创建一个带有触发器的定时任务放入队列中。

在这里插入图片描述在这里插入图片描述

  • 结论:ScheduledThreadPoolExecutor继承了ThreadPoolExecutor类,因此,整体上功能一致,线程池主要负责创建线程(Worker类),线程从阻塞队列中不断获取新的异步任务,直到阻塞队列中已经没有了异步任务为止。但是相较于ThreadPoolExecutor来说,ScheduledThreadPoolExecutor具有延时执行任务和可周期性执行任务的特性,ScheduledThreadPoolExecutor重新设计了任务类ScheduleFutureTask,ScheduleFutureTask重写了run方法使其具有可延时执行和可周期性执行任务的特性。另外,阻塞队列DelayedWorkQueue是可根据优先级排序的队列,采用了堆的底层数据结构,使得与当前时间相比,待执行时间越靠近的任务放置队头,以便线程能够获取到任务进行执行;
    链接:https://www.jianshu.com/p/502f9952c09b

newSingleThreadExecutor

  • 一个线程为1的fixedThreadExecutor

newSingleThreadScheduledExecutor

  • 一个线程核心大小为1的ScheduledThreadPool
Executor中还可以创建其他类型的线程池,下次在研究了

线程池的Executor

1.当小于核心线程数的线程在运行时,提交一个新的任务会创建一个新的线程执行任务
2.当核心线程达到指定大小时,再提交的任务会进入队列。
3.当队列已满的时候,且最大线程数大于核心线程数时,会创建新的线程执行任务
4.如果任务持续增加到大于最大线程数时会执行拒绝策略。
借用一张图片 from:https://blog.youkuaiyun.com/weixin_41910694/article/details/90676705
在这里插入图片描述

四种拒绝策略
  • AbortPolicy:直接抛出异常。(上面四种线程池默认采用的)
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
    也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
队列
  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法+ Executors.newCachedThreadPool使用了这个队列。
  • DelayedWordQueue队列是一个满足堆的排序阻塞队列,按照最先执行的时间进行排序。

在这里插入图片描述
最后在addWorker中,如果任务添加成功,最终看到的还是线程的start方法开始线程
从这里还可以复习下start和run方法的区别, 线程执行start方法后可接着执行本地方法start0,最后执行run方法才算开启了新的线程值,start方法只能说是在一个线程中开启另外一个线程,新线程的执行最终还是run。

关于submit提交方法

最近在工作中有使用到带返回的callable的使用,线程池可以通过submit提交任务,并获得一个Future,
该接口中有一个get方法获得线程的返回结果,方法会阻塞调用线程的执行。执行原理还有待深究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值