线程池

本文详细探讨了线程池的基本概念、ThreadPoolExecutor构造器参数、运行原理及常见创建方式,对比了不同线程池的特点,适合希望深入了解线程池机制的开发者。

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

一、基本概念

1. Thread的弊端
1)每次 new Thread() 新建对象,性能差

2)线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM

3)缺少更多的功能,如更多执行、定期执行、线程中断
2. 线程池的好处
1)重用存在的线程,减少对象创建、消亡的开销,性能佳,降低资源消耗

2)可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞,提高响应速度

3)提供定时执行、定期执行、单线程、并发数控制等功能,以达到提高线程的可管理性
3. 其他
1)阿里发布的 Java 开发手册中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

2)Executors利用工厂模式向我们提供了4种线程池实现方式,但是并不推荐使用,原因是使用Executors创建线程池不会传入相关参数而使用默认值所以我们常常忽略了那些重要的参数(线程池大小、缓冲队列的类型等),而且默认使用的参数会导致资源浪费,不可取

二、ThreadPoolExecutor构造器和参数(构造器中各个参数的含义)

java.uitl.concurrent.ThreadPoolExecutor 类是线程池中最核心的一个类,ThreadPoolExecutor 类继承结构是: Executor(I) <- ExecutorService(I) <- AbstractExecutorService© <- TreadPoolExecutor

1. corePoolSize: 核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了预创建线程的方法,即在没有任务到来之前就创建 corePoolSize 个线程或者 一个线程
1)prestartCoreThread() : 预创建一个核心线程,使其闲置等待工作

2)prestartAllCoreThreads() : 启动所有核心线程,导致它们空闲地等待工作

默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
2. maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程
3. keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
1)只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize

2)但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0
4. unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性
1)TimeUnit.DAYS : 以 天 为单位 ;

2)TimeUnit.HOURS : 以 小时 为单位 ;

3)TimeUnit.MINUTES : 以 分钟 为单位 ;

4)TimeUnit.SECONDS : 以 秒 为单位 ;

5)TimeUnit.MILLISECONDS : 以 毫秒 为单位 ;

6)TimeUnit.MICROSECONDS : 以 微秒 为单位 ;

7)TimeUnit.NANOSECONDS : 以 纳秒 为单位 ;
5. workQueue: 一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择
1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小

2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE

3)SynchronousQueue :这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务

一般使用LinkedBlockingQueue和SynchronousQueue
6. threadFactory:线程工厂,主要用来创建线程
1)线程池最主要的一项工作,就是在满足某些条件的情况下创建线程。而在ThreadPoolExecutor线程池中,创建线程的工作交给ThreadFactory来完成。

2)要使用线程池,就必须要指定ThreadFactory。 如果我们使用的构造函数时并没有指定使用的ThreadFactory,这个时候ThreadPoolExecutor会使用一个默认的ThreadFactory:DefaultThreadFactory(这个类在Executors工具类中)
7. handler:在ThreadPoolExecutor线程池中还有一个重要的接口:RejectedExecutionHandler
1)当提交给线程池的某一个新任务无法直接被线程池中“核心线程”直接处理,又无法加入等待队列,也无法创建新的线程执行;又或者线程池已经调用shutdown()方法停止了工作;又或者线程池不是处于正常的工作状态;这时候ThreadPoolExecutor线程池会拒绝处理这个任务,触发创建ThreadPoolExecutor线程池时定义的RejectedExecutionHandler接口的实现

2)表示当拒绝处理任务时的策略,有以下四种取值,四种值都为其静态内部类

	(1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常

	(2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常

	(3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行新提交的任务

	(4)ThreadPoolExecutor.CallerRunsPolicy: 直接调用execute来执行当前任务

三、ThreadPoolExecutor运行原理

1. ThreadPoolExecutor.execute() —— 向线程池中提交一个不需要返回结果的任务

在这里插入图片描述

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获得全局锁)

2)如果运行的线程等于或多于corePoolSize ,则将任务加入BlockingQueue

3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(需要获得全局锁)

4)如果创建新线程将使当前运行的线程超出maxiumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法

线程池采取上述的流程进行设计是为了减少获取全局锁的次数。在线程池完成预热(当前运行的线程数大于或等于corePoolSize)之后,几乎所有的execute方法调用都执行步骤2
2. ThreadPoolExecutor.submit() —— 向线程池中提交一个需要返回结果的任务

在这里插入图片描述

1)submit()原理

	(1)submit()接收任务参数,并将参数封装为FutureTask任务类

	(2)将封装好的FutureTask提交到execute()中

	submit()真正实现的任务处理流程跟execute()一样,也可以说submit()就是调用了execute()

2) 运行原理(看第一个其实就可以了)

	(1)提交任务到线程池

	(2)线程池判断核心线程池里是的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程

	(3)线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务储存在这个工作队列里。如果工作队列满了,则进入下一个流程

	(4)线程池判断其内部线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已满了,则交给饱和策略来处理这个任务

四、线程池的常见创建方式(返回值都是ExecutorService)

1. Executors.newCacheThreadPool(): 			可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况

2. Executors.newFixedThreadPool(int n): 		定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程 

3. Executors.newScheduledThreadPool(int n): 	周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。	

4. Executors.newSingleThreadExecutor(): 		创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,适用于有顺序的任务的应用场景

参考网址

JAVA并发编程与高并发解决方案 - 并发编程 六 之 线程池

4种常用线程池介绍

注:文章是经过参考其他的文章然后自己整理出来的,有可能是小部分参考,也有可能是大部分参考,但绝对不是直接转载,觉得侵权了我会删,我只是把这个用于自己的笔记,顺便整理下知识的同时,能帮到一部分人。
ps : 有错误的还望各位大佬指正,小弟不胜感激

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值