Java线程池(围绕ThreadPoolExecutor讲解⼯作流程、常见参数、调优、监
控)
1. Java的
①合理使⽤线程池的好处
Java的线程池是运⽤场景最多的并发框架,⼏乎所有需要异步或者并发执⾏任务的程序都可以使⽤线程池。
合理使⽤线程池能带来的好处:
1. 降低资源消耗。 通过重复利⽤已经创建的线程降低线程创建的和销毁造成的消耗。例如,⼯作线程Woker会⽆线循环获取阻塞队列中
的任务来执⾏。
2. 提⾼响应速度。 当任务到达时,任务可以不需要等到线程创建就能⽴即执⾏。
3. 提⾼线程的可管理性。 线程是稀缺资源,Java的线程池可以对线程资源进⾏统⼀分配、调优和监控。
②线程池的⼯作流程
⼀个新的任务到线程池时,线程池的处理流程如下:
1.线程池判断核⼼线程池⾥的线程是否都在执⾏任务。 如果不是,创建⼀个新的⼯作线程来执⾏任务。如果核⼼线程池⾥的线程都在执⾏任
务,则进⼊下个流程。
2. 线程池判断阻塞队列是否已满。 如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中。如果阻塞队列已满,则进⼊下个流程。
3. 线程池判断线程池⾥的线程是否都处于⼯作状态。 如果没有,则创建⼀个新的⼯作线程来执⾏任务。如果已满,则交给饱和策略来处理这
个任务。
线程池的核⼼实现类是ThreadPoolExecutor类,⽤来执⾏提交的任务。因此,任务提交到线程池时,具体的处理流程是
由ThreadPoolExecutor类的execute()⽅法去完成的。
1. 如果当前运⾏的线程少于corePoolSize ,则创建新的⼯作线程来执⾏任务(执⾏这⼀步骤需要获取全局锁)。
2. 如果当前运⾏的线程⼤于或等于corePoolSize ,⽽且BlockingQueue 未满,则将任务加⼊到BlockingQueue 中。
3. 如果BlockingQueue 已满,⽽且当前运⾏的线程⼩于maximumPoolSize ,则创建新的⼯作线程来执⾏任务(执⾏这⼀步骤需要获取全局
锁)。
4. 如果当前运⾏的线程⼤于或等于maximumPoolSize ,任务将被拒绝,并调⽤jectExecution()⽅法。即调⽤饱和策略对任务进⾏处理。
⼯作线程(Worker): 线程池在创建线程时,会将线程封装成⼯作线程Woker。Woker在执⾏完任务后,不是⽴即销毁⽽是循环获取阻塞队列⾥的任务来执⾏。
③ 线程池的创建(7个参数)可以通过ThreadPoolExecutor 来创建⼀个线程池:
corePoolSize(线程池的基本⼤⼩):
1. 提交⼀个任务到线程池时,线程池会创建⼀个新的线程来执⾏任务。注意: 即使有空闲的基本线程能执⾏该任务,也会创建新的线
程。
2. 如果线程池中的线程数已经⼤于或等于corePoolSize ,则不会创建新的线程。
3. 如果调⽤了线程池的prestartAllCoreThreads()⽅法,线程池会提前创建并启动所有基本线程。
maximumPoolSize(线程池的最⼤数量): 线程池允许创建的最⼤线程数。
1. 阻塞队列已满,线程数⼩于maximumPoolSize 便可以创建新的线程执⾏任务。
java线程池创建的四种2. 如果使⽤⽆界的阻塞队列,该参数没有什么效果。
workQueue(⼯作队列): ⽤于保存等待执⾏的任务的阻塞队列。1. ArrayBlockingQueue : 基于数组结构的有界阻塞队列,按FIFO(先进先出)原则对任务进⾏排序。使⽤该队列,线程池中能创建的最
⼤线程数为maximumPoolSize。2. LinkedBlockingQueue : 基于链表结构的⽆界阻塞队列,按FIFO(先进先出)原则对任务进⾏排序,吞吐量⾼于
ArrayBlockingQueue。使⽤该队列,线程池中能创建的最⼤线程数为corePoolSize。静态⼯⼚⽅法
态。静态⼯⼚⽅法 wCachedThreadPool()使⽤了这个队列。4. PriorityBlokingQueue : ⼀个⽀持优先级的⽆界阻塞队列。使⽤该队列,线程池中能创建的最⼤线程数为corePoolSize。
keepAliveTime(线程活动保持时间): 线程池的⼯作线程空闲后,保持存活的时间。如果任务多⽽且任务的执⾏时间⽐较短,可以调⼤keepAliveTime,提⾼线程的利⽤率。unit(线程活动保持时间的单位): 可选单位有DAYS、HOURS、MINUTES、毫秒、微秒、纳秒。
handler(饱和策略,或者⼜称拒绝策略): 当队列和线程池都满了,即线程池饱和了,必须采取⼀种策略处理提交的新任务。new ThreadPoolExecutor (int corePoolSize , int maximumPoolSize , long keepAliveTime , TimeUnit unit , BlockingQueue <Runnable > workQueue , RejectedExecutionHandler handler )
1
2
1. AbortPolicy : ⽆法处理新任务时,直接抛出异常,这是默认策略。
2. CallerRunsPolicy :⽤调⽤者所在的线程来执⾏任务。
3. DiscardOldestPolicy :丢弃阻塞队列中最靠前的⼀个任务,并执⾏当前任务。
4. DiscardPolicy : 直接丢弃任务。threadFactory: 构建线程的⼯⼚类
总结:
1. 常⽤的5个,核⼼池、最⼤池、空闲时间、时间的单位、阻塞队列;另外两个:拒绝策略、线程⼯⼚类
2. 常见线程池的创建参数如下。PS: CachedThreadPool核⼼池为0,最⼤池为Integer.MAX_VALUE,相当于只使⽤了最⼤池;其他
线程池,核⼼池与最⼤池⼀样⼤,因此相当于只⽤了核⼼池。
3. 如果使⽤的阻塞队列为⽆界队列,则永远不会调⽤拒绝策略,因为再多的任务都可以放在队列中。
4. SynchronousQueue 是不存储任务的,新的任务要么⽴即被已有线程执⾏,要么创建新的线程执⾏。
④ 向线程池提交任务使⽤ute()⽅法来提交任务:
⑤ 线程池的五种运⾏状态RUNNING : 该状态的线程池既能接受新提交的任务,⼜能处理阻塞队列中任务。
SHUTDOWN: 该状态的线程池不能接收新提交的任务,但是能处理阻塞队列中的任务。(政府服务⼤厅不在允许众拿号了,处理完⼿头的和排队的政务就下班。)FixedThredPool : new ThreadExcutor (n , n , 0L , ms , new LinkedBlockingQueue <Runable >()SingleThreadExecutor : new ThreadExcutor (1, 1, 0L , ms , new LinkedBlockingQueue <Runable >())CachedTheadPool : new ThreadExcutor (0, max_valuem , 60L , s , new SynchronousQueue <Runnable >());ScheduledThreadPoolExcutor : ScheduledThreadPool , SingleThreadScheduledExecutor .
1
2
3
4public void execute (Runnable command ) { // command 为null ,抛出NullPointerException if (
command == null ) throw new NullPointerException (); int c = ctl .get (); // 线程池中的线程数⼩于corePoolSize ,创建新的线程 if (workerCountOf (c ) < corePoolSize ) { if (addWorker (command , true ))// 创建⼯作线程 return ; c = ctl .get (); } // 将任务添加到阻塞队列,如果 if (isRunning (c ) && workQueue .offer (command )) { int recheck = ctl .get (); if (! isRunning (recheck ) && remove (command )) reject (command ); else if (workerCountOf (recheck ) == 0) addWorker (null , false ); }// 阻塞队列已满,尝试创建新的线程,如果超过maximumPoolSize ,执⾏jectExecution() else if (!addWorker (command , false )) reject (command );}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 处于 RUNNING 状态时,调⽤ shutdown()⽅法会使线程池进⼊到该状态。
2. 注意: finalize() ⽅法在执⾏过程中也会隐式调⽤shutdown()⽅法。
STOP: 该状态的线程池不接受新提交的任务,也不处理在阻塞队列中的任务,还会中断正在执⾏的任务。(政府服务⼤厅不再进⾏服务了,拿号、排队、以及⼿头⼯作都停⽌了。)
1. 在线程池处于 RUNNING 或 SHUTDOWN 状态时,调⽤ shutdownNow() ⽅法会使线程池进⼊到该状态;
TIDYING: 如果所有的任务都已终⽌,workerCount (有效线程数)=0 。
1. 线程池进⼊该状态后会调⽤ terminated() 钩⼦⽅法进⼊TERMINATED 状态。
TERMINATED: 在terminated()钩⼦⽅法执⾏完后进⼊该状态,默认terminated()钩⼦⽅法中什么也没有做。
⑥线程池的关闭(shutdown或者shutdownNow⽅法)
可以通过调⽤线程池的shutdown或者shutdownNow⽅法来关闭线程池:遍历线程池中⼯作线程,逐个调⽤interrupt⽅法来中断线程。
shutdown⽅法与shutdownNow的特点:
1. shutdown⽅法将线程池的状态设置为SHUTDOWN状态,只会中断空闲的⼯作线程。
2. shutdownNow⽅法将线程池的状态设置为STOP状态,会中断所有⼯作线程,不管⼯作线程是否空闲。
3. 调⽤两者中任何⼀种⽅法,都会使isShutdown⽅法的返回值为true;线程池中所有的任务都关闭后,isTerminated⽅法的返回值为true。
4. 通常使⽤shutdown⽅法关闭线程池,如果不要求任务⼀定要执⾏完,则可以调⽤shutdownNow⽅法。
2. java线程池的调优以及监控
①线程池的调优(线程池的合理配置)
先从以下⼏个⾓度分析任务的特性:
1. 任务的性质: CPU 密集型任务、IO 密集型任务和混合型任务。
2. 任务的优先级: ⾼、中、低。
3. 任务的执⾏时间: 长、中、短。
4. 任务的依赖性: 是否依赖其他系统资源,如数据库连接。
任务性质不同的任务可以⽤不同规模的线程池分开处理。 可以通过 Runtime().availableProcessors() ⽅法获得当前设备的 CPU 个数。
1. CPU 密集型任务配置尽可能⼩的线程,如配置 N c p u + 1 N_{cpu}+1 Ncpu+1 个线程的线程池。
2. IO 密集型任务则由于线程并不是⼀直在执⾏任务,则配置尽可能多的线程,如 2 ∗ N c p u 2*N_{cpu} 2∗Ncpu。
3. 混合型任务,如果可以拆分,则将其拆分成⼀个 CPU 密集型任务和⼀个 IO 密集型任务。只要这两个任务执⾏的时间相差不是太⼤,
那么分解后执⾏的吞吐率要⾼于串⾏执⾏的吞吐率;如果这两个任务执⾏时间相差太⼤,则没必要进⾏分解。
优先级不同的任务可以使⽤优先级队列 PriorityBlockingQueue 来处理,它可以让优先级⾼的任务先得到执⾏。但是,如果⼀直有⾼优先级的任务加⼊到阻塞队列中,那么低优先级的任务可能永远不能执⾏。
执⾏时间不同的任务可以交给不同规模的线程池来处理,或者也可以使⽤优先级队列,让执⾏时间短的任务先执⾏。
依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果,线程数应该设置得较⼤,这样才能更好的利⽤ CPU。
建议使⽤有界队列,有界队列能增加系统的稳定性和预警能⼒。可以根据需要设⼤⼀点,⽐如⼏千。
使⽤⽆界队列,线程池的队列就会越来越⼤,有可能会撑满内存,导致整个系统不可⽤。
② 线程池的监控
可以通过线程池提供的参数读线程池进⾏监控,有以下属性可以使⽤:1. taskCount :线程池需要执⾏的任务数量,包括已经执⾏完的、未执⾏的和正在执⾏的。2. completedTaskCount :线程池在运⾏过程中已完成的任务数量,completedTaskCount <= taskCount。3. largestPoolSize :线程池曾经创建过的最⼤线程数量,通过这个数据可以知道线程池是否满过。如等于线程池的最⼤⼤⼩,则表⽰线程
池曾经满了。4. getPoolSize: 线程池的线程数量。如果线程池不销毁的话,池⾥的线程不会⾃动销毁,所以线程池的线程数量只增不减。5. getActiveCount :获取活动的线程数。
通过继承线程池并重写线程池的 beforeExecute,afterExecute 和 terminated ⽅法,我们可以在任务执⾏前,执⾏后和线程池关闭前⼲⼀些事情。如监控任务的平均执⾏时间,最⼤执⾏时间和最⼩执⾏时间等。这⼏个⽅法在线程池⾥是空⽅法,如:
3. Java 线程池的常见问题1. 讲讲Java的线程池
基础讲解:
1. 以ThreadPoolExecutor 为切⼊点,讲解excute()⽅法中所体现的Java 线程池运⾏流程。
2. ⼯作线程Worker,它的循环⼯作特点
3. 如何新建线程池:7个参数(重点在阻塞队列和饱和策略)
进阶:
1. 线程池五个状态的特点以及如何进⾏状态之间的切换:running 、shutdown 、stop 、tidying 、terminated 。
2. 如何关闭线程:shutdown ⽅法和shutdownNow ⽅法的特点
3. 线程池的调优(针对任务的不同特性 + 建议使⽤有界队列)
4. 线程池的监控参数以及可以重写的⽅法。两种主要的线程池类型:普通的线程池ThreadPoolExecutor ,⽀持延迟或周期性执⾏的任务的线程池ScheduledThreadPoolExcutor 。讲解ThreadPoolExcutor 中5个常⽤参数+2个不常⽤参数,包含的三种线程池:创建时的参数、运⾏的流程、各⾃适合的场景。讲解ScheduledThreadPoolExecutor 的阻塞队列的原理、如何更改任务的time。提供了五种定义好的线程池,都可以通过Executors ⼯具类去调⽤,⽐如wFixedThreadPool(12)
2. 具体的场景,如果corePoolSize为x,maximumPoolSize为y,阻塞队列为z,第w个任务进来如何分配?
3. 线程池如何进⾏调优?线程池的调优(针对任务的不同特性 + 建议使⽤有界队列)
4. 线程池中的核⼼参数,超过核⼼size怎么处理,队列满怎么处理,拒绝策略有哪些?(⽐较具体)protected void beforeExecute (Thread t , Runnable r ) { }
1
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论