当前位置:网站首页 > Java基础 > 正文

java阻塞队列实现原理(java阻塞队列线程安全吗)



多线程 Part 5 - 线程池

  • 一、两种线程模型
  • 1. 用户线程ULT
  • 2. 内核线程KLT
  • 二、为什么要有线程池
  • 三、线程池的好处
  • 四、线程池使用
  • 1. 三大使用
  • 2. 七大参数
  • 3. 线程池结构
  • 4. 四种拒绝策略
  • 5. 怎么确定最大核心线程数
  • 五、线程池的五种状态
  • 六、线程池工作原理
  • execute()
  • addWorker() 上半部分
  • addWorker() 下半部分
  • runWorker()
  • getTask()
  • processWorkerExit()
  • tryTerminate()
  • shutdown()
  • shutdownNow()
  • 七、总结

由用户程序实现,不依赖于操作系统。不需要用户态与核心态的切换,速度快。内核对用户线程无感知,线程阻塞则进程阻塞。

由操作系统管理,线程阻塞不会引起进程阻塞。在多处理器下,多线程在多处理器上并行运行。效率比用户线程低。JVM基本用的是KLT。从Java创建的线程会1:1的对应到内核,然后由CPU调度执行。

创建线程和销毁线程都是很耗资源的操作,而Java线程依赖内核线程,创建线程需要进行操作系统状态切换。为了避免过度消耗资源,有了线程池,作为线程缓存的区域。

  1. 降低资源消耗
  2. 提高响应速度
  3. 方便管理
  4. 线程复用,可以控制最大并发数,管理线程

a). 单个线程

输出:

b). 创建一个固定大小的线程池,这里大小设为3,所以最多会有3个线程执行

输出:

c). 能有多少就有多少,这里输出创建了8个线程

输出:

无论是single, fixed, Cached,通通调用的都是 ThreadPoolExecutor 类来进行线程池创建

我们来看一下 ThreadPoolExecutor 类构造器,参数列表里就是传说中的七大参数

在阿里巴巴开发手册里面规定了,不要用Executors创建线程池,而是要用ThreadPoolExecutor来创建,因为确实用Executors不安全,有OOM问题。

那么我们先来举个例子看一下线程池的结构,再来讨论着七大参数分别有什么意义。

JAVA多线程堆积导致阻塞_并发编程

JAVA多线程堆积导致阻塞_java_02

JAVA多线程堆积导致阻塞_JAVA多线程堆积导致阻塞_03

JAVA多线程堆积导致阻塞_线程池_04

我们一个个来研究一下都是什么策略:

  1. AbortPolicy:直接拒绝,可以看到方法里面直接抛出了一个异常
  1. CallerRunsPolicy:让线程执行它原有的业务,可谓从哪里来回哪里去。线程池不会受理它的业务。如果线程池正好这个时候被关闭了,那进来的这个线程就回不到原来的地方了。
  1. DiscardPolicy:可以看到里什么也没做,所以进来的线程也是直接被拒绝,只不过不会报异常
  1. DiscardOldestPolicy:这个和前三个有很大的不同。如果线程池关闭了,那当然什么也不会发生。如果线程池开着,那就把阻塞队列里面第一个排队的给移走,然后尝试让线程池受理新进来的这个线程。
  • CPU密集型:把 maximumPoolSize 就设置为CPU核数
  • IO密集型:判断程序中十分占IO的任务有多少个,然后设置 maximumPoolSize 至少大于这个个数
  1. Running:能接受新任务以及处理已添加的任务
  2. Shutdown:不接受新任务,但可以处理已经添加的任务
  3. Stop:不接受新任务,不处理已经添加的任务,并且中断正在处理的任务
  4. Tidying:所有的任务已经终止
  5. Terminated:线程池彻底终止

线程池状态转换

JAVA多线程堆积导致阻塞_多线程_05

我们发现五种状态被存在一个32位的Integer里。这里高3位代表状态,低29位代表当前工作的线程数

  1. 如果当前工作线程小于核心线程数,那就创建一个核心线程worker并且负责执行传入的command任务
  2. 如果当前工作线程大于核心线程数,那就把传入的command任务放入阻塞队列里
  3. 如果阻塞队列满了,传入的command无法放入,那就尝试增加非核心线程worker
  4. 如果非核心线程数也满了,那就使用拒绝策略,拒绝传入的command任务
  5. 以上情况都是建立在线程池running状态,如果线程池准备终止了,那传入的command不会被受理,也不会有新的worker被创建

通过内外两层循环,先检查线程池当前可不可以增加新的worker,不可以增加新worker的情况有两大类:

  1. 线程池不在running状态
    a). 线程池处于Stop, Tidying, Terminated
    b). 线程池接受了新任务
    c). 阻塞队列为空







  2. 线程池在running状态
    a). 当前工作线程数已经达到了理论最大值0x1fffffff
    b). 当前工作线程数超过了核心线程数或者最大线程数(取决于参数core的值)




简单的说,如果线程池不在running状态了,那线程池要想创建新worker,必须是在线程池shutdown状态,因为这个时候线程池虽然不接受新任务,但之前已接受的任务还都会处理完。而且这个时候,创建的worker只会是非核心线程,而且只会从阻塞队列里去拿任务。如果线程池还在running状态,那线程池数量必须在要求的限制范围内。

检查后,确定可以创建新worker,那就通过CAS将工作线程数加1,真正创建新worker要到的下半部分

实质的创建了新worker,并且启动线程,让这个worker开始工作。当前线程启动的条件是线程池还处于running状态,或者处于shutdown状态而且没有接受新任务。最后返回布尔值代表线程是否成功启动。

看看Worker构造器,将传入的firstTask赋给Worker对象的Runnable变量,再将Worker对象的线程初始化,但注意,这时还没有将firstTask交给线程处理

Worker类继承了Runnable接口,真正启动线程调用的就是Worker类里的run方法

如果worker的task是空的话,那就从阻塞队列里面去取task,并且这里是个while大循环,不断地去判断是不是有事务要做,实现了线程池的复用机制。如果在阻塞队列取task超时了,那就会跳出while往finally走,准备回收当前这个worker。

在阻塞队列取task的工作在里完成;回收worker的工作在里完成

从阻塞队列里去取task。有3种情况会决定要回收当前worker:

  1. 线程池处于shutdown,而且阻塞队列为空。不会接受新任务了,也没有旧任务做了,回收
  2. 线程池处于stop。新旧任务都不会做了,回收
  3. 如果当前线程数超过了核心线程数(默认核心线程不要求回收),并且从阻塞队列取task超时,回收

注意,如果核心线程也要求回收(取决于allowCoreThreadTimeOut),那核心线程超时了也一样会被回收,哪怕最后所有线程全都回收了。

如果这里决定要回收worker了,会预先把工作线程数减1,然后由真正执行回收工作。但如果由于中断等原因,这个worker应该要回收了,但工作线程数没有减1,那工作线程数减1的工作也会在里补充完成。

如果线程是非正常结束的,那就会先将工作线程数减1。然后从workers哈希表中移除当前这个worker。再尝试终止线程池。如果当前线程池处在shutdown或者running状态,那有两种情况可能会需要再次补充创建非核心线程:

  1. 当前worker是非正常结束的
  2. 当前worker是正常结束的,但当前线程池工作线程数量小于最少需求数

线程池可以结束的条件:

  1. 线程池不在running状态
  2. 线程池不在shutdown状态;或者线程池在shutdown状态,但阻塞队列为空

线程池具备可以结束的条件之后,一次会尝试中断一个闲置线程(如果线程正在工作那就不会被中断),然后被中断的线程就会被回收,再去中断另一个线程,直到所有线程都被回收,线程池开始终止。

将线程池转为shutdown状态,然后结束线程池

同样也是结束线程池,但和不一样的是:

  1. 有返回值,返回的就是阻塞队列里没完成的任务
  2. 线程池状态先转为的是Stop
  3. 会中断所有的线程,包括正在工作的

线程池原理简单的讲就和银行办理业务的流程很相似,一开始只有核心线程受理业务,核心线程全部开启了,任务就会进阻塞队列排队,阻塞队列排满了,就会开启非核心线程,非核心线程也满了,就会启动拒绝策略。

每个工作线程处理好手头的业务之后,都会循环的去阻塞队列里取新任务来执行,实现线程的复用。

任务减少的时候,非核心线程如果长时间没有拿到任务就会被回收。但是核心线程如果不要求回收,即使没有任务,它们也会一直待在线程池里。

需要注意的是,在线程池内部,其实并没有对每个线程进行核心/非核心线程的标记。核心/非核心线程仅仅是在数量上进行了定义,而这个数量的限制规定了我们什么时候要创建新的工作线程,什么时候回收旧的线程,以及线程池里应该留下多少线程。

到此这篇java阻塞队列实现原理(java阻塞队列线程安全吗)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • java spring(JAVAspringboot项目)2025-10-13 16:54:10
  • java面试必考题(java面试常见笔试题)2025-10-13 16:54:10
  • java中字符串转为字符数组(java如何把字符串转换成字符数组)2025-10-13 16:54:10
  • java爬虫教学(java爬虫步骤)2025-10-13 16:54:10
  • 华为java面经(华为java笔试题)2025-10-13 16:54:10
  • java内存模型jmm(Java内存模型规定了哪些操作具有原子性)2025-10-13 16:54:10
  • java阻塞队列实现(java阻塞是什么意思)2025-10-13 16:54:10
  • 华为odjava面试题(华为od的hr面试内容)2025-10-13 16:54:10
  • java比较好的爬虫框架(java爬虫框架使用排行)2025-10-13 16:54:10
  • java的网站(java程序网站)2025-10-13 16:54:10
  • 全屏图片