newfixedthreadpool队列满了_JAVA线程池学习以及队列拒绝
策略
为什么要⽤线程池?
在Java中,如果每当⼀个请求到达就创建⼀个新线程,开销是相当⼤的。在实际使⽤中,每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源,甚⾄可能要⽐花在实际处理实际的⽤户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在⼀个JVM中创建太多的线程,可能会导致系统由于过度消耗内存或者“切换过度”⽽导致系统资源不⾜。为了防⽌资源不⾜,服务器应⽤程序需要⼀些办法来限制任何给定时刻处理的请求数⽬,尽可能减少创建和销毁线程的次数,特别是⼀些资源耗费⽐较⼤的线程的创建和销毁,尽量利⽤已有对象来进⾏服务,这就是“池化资源”技术产⽣的原因。
线程池主要⽤来解决线程⽣命周期开销问题和资源不⾜问题,通过对多个任务重⽤线程,线程创建的开销被分摊到多个任务上了,⽽且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以⽴即请求服务,使应⽤程序响应更快。另外,通过适当的调整线程池中的线程数据可以防⽌出现资源不⾜的情况。
ThreadPoolExecutor类
JDK 1.5以后,Java提供⼀个线程池ThreadPoolExecutor类。下⾯从构造函数来分析⼀下这个线程池的使⽤⽅法。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
参数名、说明:
* corePoolSize 线程池维护线程的最少数量
* maximumPoolSize 线程池维护线程的最⼤数量
* keepAliveTime 线程池维护线程所允许的空闲时间
* workQueue 任务队列,⽤来存放我们所定义的任务处理线程
* threadFactory 线程创建⼯⼚
* handler 线程池对拒绝任务的处理策略
ThreadPoolExecutor将根据corePoolSize和maximumPoolSize设置的边界⾃动调整池⼤⼩。当新任务
在⽅法execute(Runnable) 中提交时, 如果运⾏的线程少于corePoolSize,则创建新线程来处理请求。
如果正在运⾏的线程等于corePoolSize时,ThreadPoolExecutor优先往队列中添加任务,直到队列满了,并且没有空闲线程时才创建新的线程。如果设置的corePoolSize 和 maximumPoolSize 相同,则创建了固定⼤⼩的线程池。
keepAliveTime:当线程数达到maximumPoolSize时,经过某段时间,发现多出的线程出于空闲状态,就进⾏线程的回收。keepAliveTime就是线程池内最⼤的空闲时间。
workQueue:当核⼼线程不能都在处理任务时,新进任务被放在Queue⾥。
线程池中任务有三种排队策略:
1. 直接提交。直接提交策略表⽰线程池不对任务进⾏缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建⼀个新的线
程处理此任务。这种策略需要线程池具有⽆限增长的可能性。实现为:SynchronousQueue
2. 有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列⾥排队等待处理。有界队列(如ArrayBlockingQueue)有助于
防⽌资源耗尽,但是可能较难调整和控制。队列⼤⼩和最⼤池⼤⼩可能需要相互折衷:使⽤⼤型队列和⼩型池可以最⼤限度地降低CPU 使⽤率、操作系统资源和上下⽂切换开销,但是可能导致⼈⼯降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使⽤⼩型队列通常要求较⼤的池⼤⼩,CPU 使⽤率较⾼,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
3. ⽆界队列。使⽤⽆界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列
中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就⽆效了。)当每个任务完全独⽴于其他任务,即任务执⾏互不影响时,适合于使⽤⽆界队列;例如,在 Web 页服务器中。这种排队可⽤于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许⽆界线程具有增长的可能性。
拒绝策略:当任务源源不断的过来,⽽我们的系统⼜处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接⼝提供了拒绝任务处理的⾃定义⽅法的机会。在ThreadPoolExecutor中已经包含四种处理策略。
1. CallerRunsPolicy:线程调⽤运⾏该任务的 execute 本⾝。此策略提供简单的反馈控制机制,能够
减缓新任务的提交速度。
2. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); }}
3. 这个策略显然不想放弃执⾏任务。但是由于池中已经没有任何资源了,那么就直接使⽤调⽤该execute的线程本⾝来执⾏。(开始我总
不想丢弃任务的执⾏,但是对某些应⽤场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执⾏的,很可能导致程序没法继续跑了。需要视业务情景⽽定吧。)
4. AbortPolicy:处理程序遭到拒绝将抛出运⾏时 RejectedExecutionException
5. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException();}
6. 这种策略直接抛出异常,丢弃任务。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是⼀
种勇⽓,为了保证后续任务的正常进⾏,丢弃⼀些也是可以接收的,记得做好记录)
7. DiscardPolicy:不能执⾏的任务将被删除
8. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
9. 这种策略和AbortPolicy⼏乎⼀样,也是丢弃任务,只不过他不抛出异常。
10. DiscardOldestPolicy:如果执⾏程序尚未关闭,则位于⼯作队列头部的任务将被删除,然后重试执⾏程序(如果再次失败,则重复此
java学习资源过程)
11. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);
}}
12. 该策略就稍微复杂⼀些,在pool没有关闭的前提下⾸先丢掉缓存在队列中的最早的任务,然后重新尝试运⾏该任务。这个策略需要适
当⼩⼼。
Executors ⼯⼚
ThreadPoolExecutor是Executors类的实现,Executors类⾥⾯提供了⼀些静态⼯⼚,⽣成⼀些常⽤的线程池,主要有以下⼏个:
1. newSingleThreadExecutor:创建⼀个单线程的线程池。这个线程池只有⼀个线程在⼯作,也就是相当于单线程串⾏执⾏所有任务。如果这个唯⼀的线程因为异常结束,那么会有⼀个新的线程来替代它。此线程池保证所有任务的执⾏顺序按照任务的提交顺序执⾏。
2. newFixedThreadPool:创建固定⼤⼩的线程池。每次提交⼀个任务就创建⼀个线程,直到线程达到线程池的最⼤⼤⼩。线程池的⼤⼩⼀旦达到最⼤值就会保持不变,如果某个线程因为执⾏异常⽽结束,那么线程池会补充⼀个新线程。(我⽤的就是这个,同上所述,相当于创建了相同corePoolSize、maximumPoolSize的线程池)
3. newCachedThreadPool:创建⼀个可缓存的线程池。如果线程池的⼤⼩超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执⾏任务)的线程,当任务数增加时,此线程池⼜可以智能的添加新线程来处理任务。此线程池不会对线程池⼤⼩做限制,线程池⼤⼩完全依赖于操作系统(或者说JVM)能够创建的最⼤线程⼤⼩。
举个栗⼦
测试类:ThreadPool,创建了⼀个 newFixedThreadPool,最⼤线程数为3的固定⼤⼩线程池。然后模拟10个任务丢进去。主线程结束后会打印⼀句:主线程结束。
由上可见执⾏顺序是这样的:线程池创建了三个线程,分别执⾏任务0、1、2,由于线程创建需要⼀定时间,因此前三个线程的执⾏顺序具有⼀定随机性。此时主线程接着往线程池中塞任务,线程池已达到最⼤线程数(3),于是开始往队列⾥放。当某个线程执⾏完任务后,直接从队列⾥拉出新的任务执⾏,队列具有先进先出的特性,因此后⾯的任务执⾏是有序的。
这个看⼀下 Executors 类的源码就更明⽩了。
实际上是创建了⼀个具有固定线程数、⽆界队列的 ThreadPoolExecutor。队列⽆界,不会拒绝任务提交,因此使⽤此⽅法时,需要注意资源被耗尽的情况。
测试类:TestThreadPool,采⽤有界队列(队列⼤⼩2),和默认拒绝策略的ThreadPoolExecutor。
执⾏结果:
线程池创建了2个线程,分别执⾏任务0、1,线程池达到corePoolSize,新进任务2、3被放⼊队列中等待处理,此时队列满,⽽线程池中线程没有执⾏完任务0、1,线程池创建新的线程,执⾏新进任务4、5,达到maximumPoolSize。此时所有任务都没有执⾏结束,主线程
⼜继续提交任务,线程池进⼊默认异常策略(AbortPolicy)拒绝服务。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。