Java 线程池 ThreadPoolExecutor
目录
-
-
- 0,Java 线程状态转换
- 1,Java 线程池的三种创建方式
- 2,ThreadPoolExecutor 类的原理
-
- 1,构造方法及参数含义
- 2,一些重要方法
- 3,线程池状态
- 4,线程池模型
- 3,任务的执行过程
- 4,合理设置线程池的大小
-
0,Java 线程状态转换
1,Java 线程池的三种创建方式
newCacheThreadPool():核心线程数是 0,非核心线程数是 2^31 - 1,没有阻塞队列(不存放任务)
适合任务数比较密集,但每个任务执行时间较短的情况
newFixedThreadPool(n):核心线程数是 n,没有非核心线程,阻塞队列最大为 2^31 - 1
适用于任务量已知,相对耗时的任务
newSingleThreadExecutor():核心线程数是 1,没有非核心线程,阻塞队列最大为 2^31 - 1
适用于多个任务排队执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor( 0 , Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newFixedThreadPool( int nThreads) { return new ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor( 1 , 1 , 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } |
可以看到以上三种创建方式实际使用的都是 ThreadPoolExecutor
类,只是参数不同而已。
2,ThreadPoolExecutor 类的原理
ThreadPoolExecutor:基本的线程池实现
ScheduledThreadPoolExecutor:任务调度线程池:带有定时任务的线程池
在『任务调度线程池』功能加入之前,可以使用 java.util.Timer 来实现定时功能,Timer 的优点在于简单易用
但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务
线程池有五种状态:
RUNNING:正常运行状态,可接收新任务,可处理阻塞队列中的任务
SHUTDOWN:不会接收新任务,但会处理阻塞队列剩余任务
STOP:会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING:任务全执行完毕,活动线程为 0,即将进入终结
TERMINATED:终结状态
1,构造方法及参数含义
ThreadPoolExecutor 类位于 java.uitl.concurrent 包中,其参数含义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public ThreadPoolExecutor( int corePoolSize, // 核心线程数,核心线程就是一直存在的线程 int maximumPoolSize, // 最大线程数,表示线程池中最多能创建多少个线程 // 非核心线程数 = 最大线程数 - 核心线程数 long keepAliveTime, // 针对非核心线程而言,表示线程没有任务执行时最多保持多久时间会终止 // 默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用 // 当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到 keepAliveTime, // 则会终止,直到线程池中的线程数不超过corePoolSize // 但是如果调用了 allowCoreThreadTimeOut(boolean) 方法 // 在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用 // 直到线程池中的线程数为 0 TimeUnit unit, // 时间单位,与 keepAliveTime 配合使用,针对非核心线程 BlockingQueue<Runnable> workQueue, // 存放任务的阻塞队列 ThreadFactory threadFactory, // 创建线程的工厂,可以为线程创建时起个好名字 RejectedExecutionHandler handler // 拒绝策略 // 任务太多的时候会进行拒绝操作 // 核心线程,非核心线程,任务队列都放不下时 ) |
unit
参数有7种取值,在 TimeUni t类中有7种静态属性:
1 2 3 4 5 6 7 | TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒 |
workQueue 参数表示一个阻塞队列,用来存储等待执行的任务,有以下几种选择:
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE
SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
handler 参数表示拒绝策略,当线程池的任务缓存队列已满,并且线程池中的线程数目达到 maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。
handler 通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常(这是默认策略)
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(调用者)处理该任务
2,一些重要方法
ThreadPoolExecutor 类中的几个重要方法:
execute():向线程池提交一个任务,交由线程池去执行
submit():也是向线程池提交任务,但是和execute()方法不同,它能够返回任务执行的结果
它实际上还是调用的 execute() 方法,只不过它利用了 Future 来获取任务执行结果
invokeAll():提交一个任务集合
invokeAny(): 提交一个任务集合,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
shutdown():关闭线程池,再也不会接受新的任务
不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止
shutdownNow():关闭线程池,再也不会接受新的任务
立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
isShutdown():不在 RUNNING 状态的线程池,此方法就返回 true
isTerminated():线程池状态是否是 TERMINATED
动态调整线程池的大小:
setCorePoolSize:设置 corePoolSize
setMaximumPoolSize:设置 maximumPoolSize
还有一些方法:
getQueue()
getPoolSize()
getActiveCount()
getCompletedTaskCount()
3,线程池状态
在 ThreadPoolExecutor 中定义了一个 volatile 变量,另外定义了几个 static final变量表示线程池的各个状态:
1 2 3 4 5 | volatile int runState; // 当前线程池的状态 static final int RUNNING = 0 ; static final int SHUTDOWN = 1 ; static final int STOP = 2 ; static final int TERMINATED = 3 ; |
线程池的状态变化:
当创建线程池后,线程池处于 RUNNING 状态
当调用了 shutdown() 方法,则线程池处于 SHUTDOWN 状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕
当调用了 shutdownNow() 方法,则线程池处于 STOP 状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务
当线程池处于 SHUTDOWN 或 STOP 状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为 TERMINATED 状态
4,线程池模型
大体上由三大部分组成:
核心线程:一直存在的线程,数量为 corePoolSize
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务;当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中;
除非手动调用了 prestartAllCoreThreads() 或者 prestartCoreThread() 方法,来预创建线程,即在没有任务到来之前就创建线程。
非核心线程:临时创建的线程,会根据任务的多少来进行创建
当然总的非核心线程的数量不能大于 maximumPoolSize - corePoolSize
如果总的非核心线程的数量很大,并且任务非常多,就会创建非常多的线程
任务队列:
SynchronousQueue:只能存放一个任务
LinkedBlockingQueue:可以无限存放任务,如果任务超级多,会有内存溢出的可能
同一时刻,线程池中所能接纳的最大任务数为:maximumPoolSize + 任务队列的长度;
当超出这个范围后,如果再有新的任务过来,将会被拒绝。
线程池中的线程的初始化:
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程
在实际中,如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread():初始化一个核心线程
prestartAllCoreThreads():初始化所有核心线程
处理任务的流程:
如果当前线程池中的线程数目小于 corePoolSize,每来一个任务,就会创建一个线程去执行这个任务
如果当前线程池中的线程数目>=corePoolSize,每来一个任务,会尝试将其添加到任务缓存队列当中
若添加成功,则该任务会等待空闲线程将其取出去执行
若添加失败(一般来说是任务缓存队列已满),则会尝试创建临时线程去执行这个任务
当核心线程,任务队列,非核心线程都使用完后,如果还有新的任务过来,将会进行拒绝处理
线程复用: 当线程处理完已分配的任务后,在没有销毁之前,还会用于去处理新的任务。这样可以避免创建过多的线程。
3,任务的执行过程
源码部分是 ThreadPoolExecutor 类中的 execute 方法:
流程图如下:
4,合理设置线程池的大小
一般需要根据任务的类型来配置线程池大小:
如果是 CPU密集型任务,就需要尽量压榨 CPU,可以设为 CPU个数+1
如果是 IO密集型任务,可以设置为 CPU个数*2
这只是一个参考值,具体的设置还需要根据实际情况进行调整,可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 5. Nginx 负载均衡配置案例(附有详细截图说明++)
· Windows 提权-UAC 绕过