关于线程池的⼯作队列及新线程的流程顺序
周杰伦所有歌曲new ThreadPoolExecutor(corePoolSize, maxPoolSize , keepAliveTime ,timeUnit, workQueue,threadFactory,rejectMethod )
新线程加⼊:
1. Running 的线程⼩于 corePoolSize ,直接创建新的线程在Pool执⾏
古诗长相思2. Running 的线程等于corePoolSize ,则任务加⼊⼯作队列
3.Running 的线程等于corePoolSize,⼯作队列已满,则加⼊⼤于corePoolSize ⼩于 maxPoolSize 线程
4. 全部满,执⾏拒绝策略
三种类型:
直接提交:⼯作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程⽽不保持它们。在此,如果不存在可⽤于⽴即运⾏任务的线程,则试图把任务加⼊队列将失败,因此会构造⼀个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求⽆界maximumPo
olSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许⽆界线程具有增长的可能性。青椒鸡肉丝
⽆界队列:使⽤⽆界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就⽆效了。)当每个任务完全独⽴于其他任务,即任务执⾏互不影响时,适合于使⽤⽆界队列;例如,在 Web页服务器中。这种排队可⽤于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许⽆界线程具有增长的可能性。
有界队列:当使⽤有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防⽌资源耗尽,但是可能较难调整和控制。队列⼤⼩和最⼤池⼤⼩可能需要相互折衷:使⽤⼤型队列和⼩型池可以最⼤限度地降低 CPU 使⽤率、操作系统资源和上下⽂切换开销,但是可能导致⼈⼯降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使⽤⼩型队列通常要求较⼤的池⼤⼩,CPU使⽤率较⾼,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
BlockingQueue的选择
例⼦⼀:使⽤直接提交策略,也即SynchronousQueue
范字组词⾸先SynchronousQueue是⽆界的,也就是说他存数任务的能⼒是没有限制的,但是由于该Queue本⾝的特性,在某次添加元素后必须等待其他线程取⾛后才能继续添加。在这⾥不是核⼼线程便是新创建的线程,但是我们试想⼀样下,下⾯的场景。
我们使⽤⼀下参数构造ThreadPoolExecutor
Java代码
1. new ThreadPoolExecutor(2, 3, 30, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new RecorderThreadFactory(
2. "CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());
!!RecorderThreadFactory(API没有这个?!)
当核⼼线程已经有2个正在运⾏:
1. 此时继续来了⼀个任务(A),根据前⾯介绍的“如果运⾏的线程等于或多于 corePoolSize,则Executor始终⾸选将请求加⼊队列,⽽乱亲不乱族
不添加新的线程。”,所以A被添加到queue中。
2. ⼜来了⼀个任务(B),且核⼼2个线程还没有忙完,OK,接下来⾸先尝试1中描述,但是由于使⽤的SynchronousQueue,所以⼀定
⽆法加⼊进去。
3. 此时便满⾜了上⾯提到的“如果⽆法将请求加⼊队列,则创建新的线程,除⾮创建此线程超出maximumPoolSize,在这种情况下,任务
将被拒绝。”,所以必然会新建⼀个线程来运⾏这个任务。农夫和蛇
4. 暂时还可以,但是如果这三个任务都还没完成,连续来了两个任务,第⼀个添加⼊queue中,后⼀个呢?queue中⽆法插⼊,⽽线程数
达到了maximumPoolSize,所以只好执⾏异常策略了。
所以在使⽤SynchronousQueue通常要求maximumPoolSize是⽆界的,这样就可以避免上述情况发⽣(如果希望限制就直接使⽤有界队列)。对于使⽤SynchronousQueue的作⽤jdk中写的很清楚:此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。
什么意思?如果你的任务A1,A2有内部关联,A1需要先运⾏,那么先提交A1,再提交A2,当使⽤Sy
nchronousQueue我们可以保证,A1必定先被执⾏,在A1么有被执⾏前,A2不可能添加⼊queue中。
例⼦⼆:使⽤⽆界队列策略,即LinkedBlockingQueue
这个就拿newFixedThreadPool来说,根据前⽂提到的规则:
你画我猜游戏规则如果运⾏的线程少于 corePoolSize,则 Executor 始终⾸选添加新的线程,⽽不进⾏排队。那么当任务继续增加,会发⽣什么呢?
如果运⾏的线程等于或多于 corePoolSize,则 Executor 始终⾸选将请求加⼊队列,⽽不添加新的线程。OK,此时任务变加⼊队列之中了,那什么时候才会添加新线程呢?
如果⽆法将请求加⼊队列,则创建新的线程,除⾮创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。这⾥就很有意思了,可能会出现⽆法加⼊队列吗?不像SynchronousQueue那样有其⾃⾝的特点,对于⽆界队列来说,总是可以加⼊的(资源耗尽,当然另当别论)。换句说,永远也不会触发产⽣新的线程!corePoolSize⼤⼩的线程数会⼀直运⾏,忙完当前的,就从队列中拿任务开始运⾏。所以要防⽌任务疯长,⽐如任务运⾏的实⾏⽐较长,⽽添加任务的速度远远超过处理任务的时间,⽽且还不断增加,不⼀会⼉就爆了。
例⼦三:有界队列,使⽤ArrayBlockingQueue
这个是最为复杂的使⽤,所以JDK不推荐使⽤也有些道理。与上⾯的相⽐,最⼤的特点便是可以防⽌资源耗尽的情况发⽣。
举例来说,请看如下构造⽅法:
Java代码
1. new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2),
2. new RecorderThreadFactory("CookieRecorderPool"), new ThreadPoolExecutor.CallerRunsPolicy());
假设,所有的任务都永远⽆法执⾏完。最大的蟒蛇
对于⾸先来的A,B来说直接运⾏,接下来,如果来了C,D,他们会被放到queue中,如果接下来再来E,F,则增加线程运⾏E,F。但是如果再来任务,队列⽆法再接受了,线程数也到达最⼤的限制了,所以就会使⽤拒绝策略来处理。