ThreadPoolExecutor

来源:互联网 发布:西安长安大学淘宝地址 编辑:程序博客网 时间:2024/04/29 23:32

先看一副图,描述了ThreadPoolExecutor的工作机制: 

ThreadPoolExecutor几点使用建议

 

整个ThreadPoolExecutor的任务处理有4步操作:

 

  • 第一步,初始的poolSize < corePoolSize,提交的runnable任务,会直接做为new一个Thread的参数,立马执行
  • 第二步,当提交的任务数超过了corePoolSize,就进入了第二步操作。会将当前的runable提交到一个block queue中
  • 第三步,如果block queue是个有界队列,当队列满了之后就进入了第三步。如果poolSize < maximumPoolsize时,会尝试new 一个Thread的进行救急处理,立马执行对应的runnable任务
  • 第四步,如果第三步救急方案也无法处理了,就会走到第四步执行reject操作。
几点说明:(相信这些网上一搜一大把,我这里简单介绍下,为后面做一下铺垫)
  • block queue有以下几种实现:
    1. ArrayBlockingQueue :  有界的数组队列
    2. LinkedBlockingQueue : 可支持有界/无界的队列,使用链表实现
    3. PriorityBlockingQueue : 优先队列,可以针对任务排序
    4. SynchronousQueue : 队列长度为1的队列,和Array有点区别就是:client thread提交到block queue会是一个阻塞过程,直到有一个worker thread连接上来poll task。
  • RejectExecutionHandler是针对任务无法处理时的一些自保护处理:
    1. Reject 直接抛出Reject exception
    2. Discard 直接忽略该runnable,不可取
    3. DiscardOldest 丢弃最早入队列的的任务
    4. CallsRun 直接让原先的client thread做为worker线程,进行执行

容易被人忽略的点:
1.  pool threads启动后,以后的任务获取都会通过block queue中,获取堆积的runnable task.

所以建议:  block size >= corePoolSize ,不然线程池就没任何意义
2.  corePoolSize 和 maximumPoolSize的区别, 和大家正常理解的数据库连接池不太一样。
  *  据dbcp pool为例,会有minIdle , maxActive配置。minIdle代表是常驻内存中的threads数量,maxActive代表是工作的最大线程数。
  *  这里的corePoolSize就是连接池的maxActive的概念,它没有minIdle的概念(每个线程可以设置keepAliveTime,超过多少时间多有任务后销毁线程,但不会固定保持一定数量的threads)。 
  * 这里的maximumPoolSize,是一种救急措施的第一层。当threadPoolExecutor的工作threads存在满负荷,并且block queue队列也满了,这时代表接近崩溃边缘。这时允许临时起一批threads,用来处理runnable,处理完后立马退出。

所以建议:   maximumPoolSize >= corePoolSize =期望的最大线程数。 (我曾经配置了corePoolSize=1, maximumPoolSize=20, blockqueue为无界队列,最后就成了单线程工作的pool。典型的配置错误)

3. 善用blockqueue和reject组合. 这里要重点推荐下CallsRun的Rejected Handler,从字面意思就是让调用者自己来运行。
我们经常会在线上使用一些线程池做异步处理,比如我前面做的 (业务层)异步并行加载技术分析和设计, 将原本串行的请求都变为了并行操作,但过多的并行会增加系统的负载(比如软中断,上下文切换)。所以肯定需要对线程池做一个size限制。但是为了引入异步操作后,避免因在block queue的等待时间过长,所以需要在队列满的时,执行一个callsRun的策略,并行的操作又转为一个串行处理,这样就可以保证尽量少的延迟影响。

所以建议:   RejectExecutionHandler = CallsRun ,  blockqueue size = 2 * poolSize (为啥是2倍poolSize,主要一个考虑就是瞬间高峰处理,允许一个thread等待一个runnable任务)

--------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------

这样的过程说明,并不是先加入任务就一定会先执行——假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17~20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。

 

了解了工作流程,看看几个参数的意义和用法:

workQueue:所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互

排队有三种通用策略:

     直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集合时出现锁定。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

     无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙的情况下将新任务加入队列。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

     有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

handler: 线程池处理满仓状态的策略,预定义四种:

     ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException

     ThreadPoolExecutor.DiscardPolicy:不能执行的任务将被删除。

     ThreadPoolExecutor.CallerRunsPolicy:线程调用运行该任务的execute本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

     ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

 

下面是一个简单的使用ThreadPoolExecutor的例子:

[java] view plaincopy
  1. package gss.study.thread;  
  2.   
  3. import java.util.concurrent.ArrayBlockingQueue;  
  4. import java.util.concurrent.ThreadPoolExecutor;  
  5. import java.util.concurrent.TimeUnit;  
  6.   
  7. import gss.util.S;  
  8.   
  9. /** 
  10.  * 这个例子也可以体会ThreadLocal的作用和用法 
  11.  * @author gss 
  12.  * 
  13.  */  
  14. public class Admin implements Runnable{  
  15.     Worker worker = null;  
  16.     int no ;  
  17.     public Admin(Worker worker,int no) {  
  18.         this.worker = worker;  
  19.         this.no=no;  
  20.     }  
  21.   
  22.     @Override  
  23.     public void run() {  
  24.         this.worker.setNo(no);  
  25.         this.worker.work();  
  26.     }  
  27.       
  28.     public static void main(String[] args) {  
  29.           
  30.         //如果是以下设置,则输出为  
  31.         /* 
  32.         pool-1-thread-1 普通参数 4 
  33.         pool-1-thread-1 ThreadLocal 0 
  34.         pool-1-thread-2 普通参数 1 
  35.         pool-1-thread-2 ThreadLocal 1 
  36.         pool-1-thread-1 普通参数 3 
  37.         pool-1-thread-1 ThreadLocal 3 
  38.         pool-1-thread-4 普通参数 4 
  39.         pool-1-thread-4 ThreadLocal 4 
  40.         pool-1-thread-5 普通参数 5 
  41.         pool-1-thread-5 ThreadLocal 5 
  42.         pool-1-thread-3 普通参数 2 
  43.         pool-1-thread-3 ThreadLocal 2 
  44.          */  
  45.         //可以体会这些参数的作用,最大pool为5,队列为1,销毁时间是1s,策略是discard,则在满六个之后后面的被丢弃  
  46. //      ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 1, TimeUnit.SECONDS,   
  47. //              new ArrayBlockingQueue(1), new ThreadPoolExecutor.DiscardPolicy());  
  48.           
  49.           
  50.         //换了策略,输出为:  
  51.         /*   
  52.          *  pool-1-thread-5 普通参数 5 
  53.             pool-1-thread-5 ThreadLocal 5 
  54.             pool-1-thread-1 普通参数 5 
  55.             pool-1-thread-1 ThreadLocal 0 
  56.             pool-1-thread-3 普通参数 5 
  57.             pool-1-thread-3 ThreadLocal 2 
  58.             pool-1-thread-2 普通参数 5 
  59.             pool-1-thread-2 ThreadLocal 1 
  60.             pool-1-thread-4 普通参数 5 
  61.             pool-1-thread-4 ThreadLocal 4 
  62.             main 普通参数 5 
  63.             main ThreadLocal 6 
  64.             pool-1-thread-5 普通参数 7 
  65.             pool-1-thread-5 ThreadLocal 3 
  66.             pool-1-thread-1 普通参数 7 
  67.             pool-1-thread-1 ThreadLocal 7 
  68.             main 普通参数 7 
  69.             main ThreadLocal 8 
  70.             pool-1-thread-6 普通参数 9 
  71.             pool-1-thread-6 ThreadLocal 9 
  72.          */  
  73.         //调用了主线程来完成工作,这个选项可以较好的完成  
  74. //      ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MICROSECONDS,   
  75. //              new ArrayBlockingQueue(1), new ThreadPoolExecutor.CallerRunsPolicy());  
  76.           
  77.         //输出为  
  78.         /* 
  79.          * Exception in thread "main" java.util.concurrent.RejectedExecutionException 
  80.                 at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1768) 
  81.                 at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:767) 
  82.                 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:658) 
  83.                 at gss.study.thread.Root.main(Root.java:78) 
  84.             pool-1-thread-3 普通参数 4 
  85.             pool-1-thread-1 普通参数 4 
  86.             pool-1-thread-1 ThreadLocal 0 
  87.             pool-1-thread-4 普通参数 4 
  88.             pool-1-thread-4 ThreadLocal 4 
  89.             pool-1-thread-2 普通参数 4 
  90.             pool-1-thread-2 ThreadLocal 1 
  91.             pool-1-thread-5 普通参数 4 
  92.             pool-1-thread-5 ThreadLocal 5 
  93.             pool-1-thread-3 ThreadLocal 2 
  94.             pool-1-thread-1 普通参数 3 
  95.             pool-1-thread-1 ThreadLocal 3 
  96.          */  
  97.         //可以看到AbortPolicy的作用,抛出异常!  
  98. //      ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MICROSECONDS,   
  99. //              new ArrayBlockingQueue(1), new ThreadPoolExecutor.AbortPolicy());  
  100.           
  101.         //把队列设的足够大,则都能处理  
  102.         /* 
  103.          * pool-1-thread-3 普通参数 2 
  104.             pool-1-thread-3 ThreadLocal 2 
  105.             pool-1-thread-2 普通参数 2 
  106.             pool-1-thread-2 ThreadLocal 1 
  107.             pool-1-thread-1 普通参数 2 
  108.             pool-1-thread-1 ThreadLocal 0 
  109.             pool-1-thread-2 普通参数 5 
  110.             pool-1-thread-2 ThreadLocal 4 
  111.             pool-1-thread-1 普通参数 5 
  112.             pool-1-thread-1 ThreadLocal 5 
  113.             pool-1-thread-3 普通参数 5 
  114.             pool-1-thread-3 ThreadLocal 3 
  115.             pool-1-thread-2 普通参数 8 
  116.             pool-1-thread-2 ThreadLocal 6 
  117.             pool-1-thread-3 普通参数 9 
  118.             pool-1-thread-3 ThreadLocal 8 
  119.             pool-1-thread-1 普通参数 9 
  120.             pool-1-thread-1 ThreadLocal 7 
  121.             pool-1-thread-2 普通参数 9 
  122.             pool-1-thread-2 ThreadLocal 9 
  123.          */  
  124.         //所以线程池的效果,要根据特定的应用场景选择合适的设置  
  125.         ThreadPoolExecutor tpe = new ThreadPoolExecutor(351, TimeUnit.MICROSECONDS,   
  126.                 new ArrayBlockingQueue(100), new ThreadPoolExecutor.AbortPolicy());  
  127.           
  128.         Worker w = new Worker();  
  129.         for (int i=0;i<10;i++) {  
  130.             Admin m = new Admin(w,i);  
  131.             tpe.execute(m);  
  132.         }  
  133.           
  134.         tpe.shutdown();  
  135.     }  
  136. }  
  137.   
  138. class Worker {  
  139.     int no;  
  140.     ThreadLocal<Number> threadLocalNo = new ThreadLocal<Number>();  
  141.       
  142.       
  143.     public int getNo() {  
  144.         return no;  
  145.     }  
  146.     public void setNo(int no) {  
  147.         this.no = no;  
  148.         this.threadLocalNo.set(no);  
  149.     }  
  150.       
  151.     public void work() {  
  152.         try {  
  153.             Thread.currentThread().sleep(1000);  
  154.         } catch (InterruptedException e) {  
  155.             // TODO Auto-generated catch block  
  156.             e.printStackTrace();  
  157.         }  
  158.         S.echo(Thread.currentThread().getName()+" 普通参数 "+this.no);  
  159.         S.echo(Thread.currentThread().getName()+" ThreadLocal "+this.threadLocalNo.get());  
  160.     }  
  161. }  


原创粉丝点击