ThreadPoolExecutor 创建线程池
/**
* public ThreadPoolExecutor(
* int corePoolSize, //--线程池核心池的大小。
* int maximumPoolSize, //--线程池的最大线程数。
* long keepAliveTime, //--当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
* TimeUnit unit, //--keepAliveTime(线程存活时间) 的时间单位。
* BlockingQueue<Runnable> workQueue, //--用来储存等待执行任务的队列。
* ThreadFactory threadFactory, //--线程工厂。
* RejectedExecutionHandler handler //--拒绝策略。
* )
*/
a、 线程池大小
1、线程池有两个线程数的设置,一个为核心池线程数,一个为最大线程数。
2、在创建了线程池后,默认情况下,线程池中并没有任何线程,等到有任务来才创建线程去执行任务,除非调用了 prestartAllCoreThreads()或者prestartCoreThread()方法
3、当创建的线程数等于 corePoolSize 时,会加入设置的阻塞队列。当队列满时,会创建线程执行任务直到线程池中的数量等于maximumPoolSize。
b、 适当的阻塞队列
线程池中任务有三种排队策略:
1、直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue
2、有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
3、无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
java.lang.IllegalStateException: Queue full
方法 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用
ArrayBlockingQueue --一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue --一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue --一个支持优先级排序的无界阻塞队列。
DelayQueue -- 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue -- 一个不存储元素的阻塞队列。
LinkedTransferQueue -- 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque -- 一个由链表结构组成的双向阻塞队列。
c、 明确拒绝策略
拒绝策略:当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接口提供了拒绝任务处理的自定义方法的机会。
在ThreadPoolExecutor中已经包含四种处理策略。
new ThreadPoolExecutor.AbortPolicy() --丢弃任务并抛出RejectedExecutionException异常。 (默认)
new ThreadPoolExecutor.DiscardPolicy() --也是丢弃任务,但是不抛出异常。
new ThreadPoolExecutor.DiscardOldestPolicy() --丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
new ThreadPoolExecutor.CallerRunsPolicy() --由调用线程处理该任务
1、AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException();}
这种策略直接抛出异常,丢弃任务。(jdk默认策略,队列满并线程满时直接拒绝添加新任务,并抛出异常,所以说有时候放弃也是一种勇气,为了保证后续任务的正常进行,丢弃一些也是可以接收的,记得做好记录)
2、DiscardPolicy:不能执行的任务将被删除
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。
3、DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) {e.getQueue().poll();e.execute(r); }}
该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。
4、CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); }}
这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。(开始我总不想丢弃任务的执行,但是对某些应用场景来讲,很有可能造成当前线程也被阻塞。如果所有线程都是不能执行的,很可能导致程序没法继续跑了。需要视业务情景而定吧。)