Java线程池相关参数以及拒绝策略
为什么要使⽤线程池
在JavaSE中,要实现多线程的⽅式有多种,例如继承Thread、实现Runnable接⼝或者Callable接⼝。但是我们应该要知道,创建⼀个线程的开销是很⼤的,因为它总涉及与操作系统交互,我们知道,线程总共有三个过程,分别是创建、使⽤、销毁。但是在实际使⽤中,服务器在创建和销毁线程上花费的时间和消耗的系统资源,甚⾄可能要⽐花在实际处理实际的⽤户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在⼀个JVM中创建太多的线程,可能会导致系统由于过度消耗内存或者“切换过度”⽽导致系统资源不⾜。为了防⽌资源不⾜,服务器应⽤程序需要⼀些办法来限制任何给定时刻处理的请求数⽬,尽可能减少创建和销毁线程的次数,特别是⼀些资源耗费⽐较⼤的线程的创建和销毁,尽量利⽤已有对象来进⾏服务,这就是“池化资源”技术产⽣的原因。
线程池主要⽤来解决线程⽣命周期开销问题和资源不⾜问题,通过对多个任务重⽤线程,线程创建的开销被分摊到多个任务上了,⽽且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以⽴即请求服务,使应⽤程序响应更快。另外,通过适当的调整线程池中的线程数据可以防⽌出现资源不⾜的情况。
也就是说,如果⼀个线程的创建时间为T1,,执⾏任务时间为T2,销毁时间为T3,如果T1+T2>T3,那
么我们就需要使⽤池化的技术来尽可能缩短T1和T3,从⽽提⾼服务器性能,把T1和T3分别安排到服务器启动和技术或者⼀些空闲阶段,这样,服务器在处理请求时就不会有T1和T3的开销了。
使⽤线程池有如下有点:
1. 降低资源消耗:通过重复利⽤已创建的线程降低线程创建和销毁造成的资源消耗。
2. 提⾼响应速度:当任务到达时,任务可以不需要等到线程创建就能⽴即执⾏
3. 提⾼线程的可管理性:线程是稀缺资源,如果⽆限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使⽤线程池可以进⾏统
⼀分配、调优和监控。
线程池的⼯作原理
ThreadPoolExecutor的构造⽅法
当向线程池中提交⼀个任务后,线程池是如何来处理这个任务的?Java提供⼀个线程池ThreadPoolExecutor类,我们从ThreadPoolExecutor的构造函数来分析⼀下这个线程池的使⽤⽅法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
在上⾯的构造⽅法中,在该⽅法中有7个参数,我分别和说说这7个参数
1. corePoolSize: 线程池维护线程的最少数量(也叫核⼼线程池数量)
2. maximumPoolSize:线程池维护线程的最⼤数量
3. keepAliveTime: 线程池维护线程所允许的空闲时间
4. unit: 线程池维护线程所允许的空闲时间的单位
5. workQueue: 线程池所使⽤的缓冲队列
6. threadFactory:线程创建的⼯⼚
7. handler: 线程池对拒绝任务的处理策略
⼀个任务通过 execute(Runnable)⽅法被添加到线程池,任务就是⼀个 Runnable类型的对象,任务的执⾏⽅法就是 Runnable类型对象的run()⽅法。
线程池的⼯作流程
当⼀个任务通过execute(Runnable)⽅法欲添加到线程池时:
1. 如果此时线程池中的数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放⼊缓冲队列。
3. 如果此时线程池中的数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maximumPoolSize,建新的线程
来处理被添加的任务。
4. 如果此时线程池中的数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过
handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核⼼线程corePoolSize、任务队列workQueue、最⼤线程maximumPoolSize,如果三者都满了,使⽤handler处理被拒绝的任务。
5. 当线程池中的线程数量⼤于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终⽌。这样,线程池可以动态的调
整池中的线程数。
举个例⼦来说明⼀下线程池中的⼯作流程:
假设队列⼤⼩为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加⼊ 20 个任务时,执⾏的顺序就是这样的:⾸先执⾏任务1、2、3,然后任务 4~13 被放⼊队列。这时候队列满了,任务 14、1
5、16 会被马上执⾏,⽽任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。
线程池的拒绝策略
当任务源源不断的过来,⽽我们的系统⼜处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接⼝提供了拒绝任务处理的⾃定义⽅法的机会。在ThreadPoolExecutor中已经包含四种处理策略。
AbortPolicy
DiscardPolicy
DiscardOldestPolicy
CallerRunsPolicy
⾃定义
来分别说说这五种拒绝策略
1.AbortPolicy:该策略是线程池默认的策略,使⽤策略时,如果线程池的线程数满了,如果再有⼀个的任务进来,那么线程池会丢掉这个任务并且抛出RejectedExecutionException异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//不做任何处理,直接抛出异常
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
}
2.DiscardPolicy:这个策略和AbortPolicy的slient版本,如果线程池队列满了,再有新的任务进来,线程池则会直接丢掉这个任务并且不会有任何异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//就是⼀个空的⽅法
}
3.DiscardOldestPolicy:这个策略从字⾯上也很好理解,丢弃最⽼的。也就是说如果队列满了,会将最早进⼊队列的任务删掉腾出空间,再尝试加⼊队列。因为队列是队尾进,队头出,所以队头元素是最⽼的,因此每次都是移除对头元素后再尝试⼊队。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
//移除队头元素
//再尝试⼊队
}
}
4.CallerRunsPolicy:使⽤此策略,如果添加到线程池失败,那么主线程会⾃⼰去执⾏该任务,不会等待线程池中的线程去执⾏。就像是个急脾⽓的⼈,我等不到别⼈来做这件事就⼲脆⾃⼰⼲。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
//直接执⾏run⽅法
r.run();
}
}
5.⾃定义策略,如果在使⽤过程中,Java对我们提供给我们的四种策略都不符合我们的要求,那我们可以⾃定义策略。⾃定义需要实现RejectedExecutionHandler接⼝,并重写其rejectedExecution⽅法,具体的策略代码我们写在rejectedExecution⽅法中就可以了。
public class MyRejectPolicy implements RejectedExecutionHandler{
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//Sender是我的Runnable类,⾥⾯有message字段
if (r instanceof Sender) {
java线程池创建的四种Sender sender = (Sender) r;
//直接打印
System.out.Message());
}
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论