`
marb
  • 浏览: 419840 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

线程池ThreadPoolExecutor使用简介

    博客分类:
  • JAVA
 
阅读更多
01 ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize,
02                                 maximumPoolSize,
03                                 keepAliveTime,
04                                 unit,
05                                 workQueue,
06                                 handler);
07 //corePoolSize: 线程池维护线程的最少数量  
08 //maximumPoolSize:线程池维护线程的最大数量  
09 //keepAliveTime: 线程池维护线程所允许的空闲时间  
10 //unit: 线程池维护线程所允许的空闲时间的单位  
11 //workQueue: 线程池所使用的缓冲队列  
12 //handler: 线程池对拒绝任务的处理策略 

 

一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。

  1. 线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个ThreadPoolExecutor还维护着一些基本的统计数据,如完成的任务数。
  2. 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导:
  3. 核心和最大池大小ThreadPoolExecutor将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和setMaximumPoolSize(int) 进行动态更改。
  4. 按需构造默认情况下,即使核心线程最初只是在新任务到达时才创建和启动的,也可以使用方法 prestartCoreThread() 或 prestartAllCoreThreads() 对其进行动态重写。如果构造带有非空队列的池,则可能希望预先启动线程。
  5. 创建新线程使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从newThread返回 null 时ThreadFactory未能创建线程,则执行程序将继续运行,但不能执行任何任务。
  6. 保持活动时间如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见 getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0, allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。
  7. 排队所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

参数介绍:
        corePoolSize 核心线程数,指保留的线程池大小(不超过maximumPoolSize值时,线程池中最多有corePoolSize 个线程工作)。 
        maximumPoolSize 指的是线程池的最大大小(线程池中最大有corePoolSize 个线程可运行)。 
        keepAliveTime 指的是空闲线程结束的超时时间(当一个线程不工作时,过keepAliveTime 长时间将停止该线程)。 
        unit 是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值)。 
        workQueue 表示存放任务的队列(存放需要被线程池执行的线程队列)。 
        handler 拒绝策略(添加任务失败后如何处理该任务).
        1、线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
        2、当调用 execute() 方法添加一个任务时,线程池会做如下判断:
            a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
            b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
            c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
            d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
        3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
        4、当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

              这个过程说明,并不是先加入任务就一定会先执行。假设队列大小为 4,corePoolSize为2,maximumPoolSize为6,那么当加入15个任务时,执行的顺序类似这样:首先执行任务 1、2,然后任务3~6被放入队列。这时候队列满了,任务7、8、9、10 会被马上执行,而任务 11~15 则会抛出异常。最终顺序是:1、2、7、8、9、10、3、4、5、6。当然这个过程是针对指定大小的ArrayBlockingQueue<Runnable>来说,如果是LinkedBlockingQueue<Runnable>,因为该队列无大小限制,所以不存在上述问题。

一:排队有三种通用策略:

  1. 直接提交工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
  • 拒绝的任务当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被 拒绝。在以上两种情况下,execute方法都将调用其 RejectedExecutionHandler的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:
  1. 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
  2. 在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的execute本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
  3. 在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
  4. 在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。

定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

  • 钩子 (hook) 方法此类提供protected可重写的 beforeExecute(java.lang.Thread, java.lang.Runnable) 和 afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。如果钩子 (hook) 或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。
  • 队列维护方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。 remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。
  • 终止程序 AND 不再引用的池没有剩余线程会自动shutdown。如果希望确保回收取消引用的池(即使用户忘记调用 shutdown()),则必须安排未使用的线程最终终止:设置适当保持活动时间,使用 0 核心线程的下边界和/或设置allowCoreThreadTimeOut(boolean)。   

    那么线程池的排除策略是什么样呢,一般按如下规律执行:

        A.  如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
        B.  如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
        C.  如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

 

   处理任务的优先级为: 

         核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。  

         当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。  

         unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:  NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。  

         workQueue我常用的是:java.util.concurrent.LinkedBlockingQueue<E>  

  handler有四个选择: <根据自己的业务来选择> 
    1:ThreadPoolExecutor.AbortPolicy()  
        抛出java.util.concurrent.RejectedExecutionException异常  
    2:ThreadPoolExecutor.CallerRunsPolicy()  
        重试添加当前的任务,他会自动重复调用execute()方法  
    3:ThreadPoolExecutor.DiscardOldestPolicy()  
        抛弃旧的任务  
   4:ThreadPoolExecutor.DiscardPolicy()  
        抛弃当前的任务 

 

总结:

  1.  线程池可立即运行的最大线程数 即maximumPoolSize 参数。
  2.  线程池能包含的最大线程数 = 可立即运行的最大线程数 + 线程队列大小 (一部分立即运行,一部分装队列里等待)
  3.  核心线程数可理解为建议值,即建议使用的线程数,或者依据CPU核数
  4.  add,offer,put三种添加线程到队列的方法只在队列满的时候有区别,add为抛异常,offer返回boolean值,put直到添加成功为止。
  5. 同理remove,poll, take三种移除队列中线程的方法只在队列为空的时候有区别, remove为抛异常,poll为返回boolean值, take等待直到有线程可以被移除。

      

扩展示例。此类的大多数扩展可以重写一个或多个受保护的钩子 (hook) 方法。例如,下面是一个添加了简单的暂停/恢复功能的子类: 

01 class PausableThreadPoolExecutor extends ThreadPoolExecutor {
02    private boolean isPaused;
03    private ReentrantLock pauseLock = new ReentrantLock();
04    private Condition unpaused = pauseLock.newCondition();
05  
06    public PausableThreadPoolExecutor(...) { super(...); }
07   
08    protected void beforeExecute(Thread t, Runnable r) {
09      super.beforeExecute(t, r);
10      pauseLock.lock();
11      try {
12        while (isPaused) unpaused.await();
13      catch(InterruptedException ie) {
14        t.interrupt();
15      finally {
16        pauseLock.unlock();
17      }
18    }
19   
20    public void pause() {
21      pauseLock.lock();
22      try {
23        isPaused = true;
24      finally {
25        pauseLock.unlock();
26      }
27    }
28   
29    public void resume() {
30      pauseLock.lock();
31      try {
32        isPaused = false;
33        unpaused.signalAll();
34      finally {
35        pauseLock.unlock();
36      }
37    }
38  }

下面是ThreadPoolExecutor例子:

01 package com.thread.threadpool;
02  
03 import java.util.concurrent.BlockingQueue;
04 import java.util.concurrent.LinkedBlockingQueue;
05 import java.util.concurrent.RejectedExecutionHandler;
06 import java.util.concurrent.ThreadPoolExecutor;
07 import java.util.concurrent.TimeUnit;
08 import java.util.concurrent.atomic.AtomicLong;
09  
10 public class ThreadPool {
11     private int corePoolSize = 1// 线程池维护线程的最少数量
12     private int maximumPoolSize = 10;// 线程池维护线程的最大数量
13     private long keepAliveTime = 3// 线程池维护线程所允许的空闲时间
14     private TimeUnit unit = TimeUnit.SECONDS;// 线程池维护线程所允许的空闲时间的单位
15     private BlockingQueue<Runnable> workQueue; // 线程池所使用的缓冲队列
16     private RejectedExecutionHandler handler; // 线程池对拒绝任务的处理策略
17     private static AtomicLong along = new AtomicLong(0);
18  
19     public void run() throws InterruptedException {
20         ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize,
21                 maximumPoolSize, keepAliveTime, unit,
22                 new LinkedBlockingQueue<Runnable>(),
23                 new ThreadPoolExecutor.DiscardOldestPolicy()) {
24  
25             // 线程执行之前运行
26             @Override
27             protected void beforeExecute(Thread t, Runnable r) {
28                 System.out.println("...............beforeExecute");
29             }
30  
31             // 线程执行之后运行
32             @Override
33             protected void afterExecute(Runnable r, Throwable t) {
34                 System.out.println("...............afterExecute");
35             }
36  
37             // 整个线程池停止之后
38             protected void terminated() {
39                 System.out.println("...............thread stop");
40             }
41         };
42         for (int i = 1; i <= 10; i++) {
43             pool.execute(new ThreadPoolTask(i, along));
44         }
45         for (int i = 1; i <= 10; i++) {
46             pool.execute(new ThreadPoolTask(-i, along));
47         }
48         pool.shutdown();
49         Thread.sleep(25000);
50         System.out.println(along.get());
51  
52     }
53  
54     public static void main(String[] args) {
55         try {
56             new ThreadPool().run();
57         catch (InterruptedException e) {
58             e.printStackTrace();
59         }
60     }
61 }
62  
63 class ThreadPoolTask implements Runnable {
64     private int i = 0;
65     private AtomicLong along;
66  
67     ThreadPoolTask(int i, AtomicLong along) {
68         this.i = i;
69         this.along = along;
70     }
71  
72     @Override
73     public void run() {
74         try {
75             // 模拟业务逻辑
76             Thread.sleep(1000);
77             along.addAndGet(i);
78         catch (InterruptedException e) {
79             e.printStackTrace();
80         }
81         System.out.println(Thread.currentThread().getName() + "  " + i);
82     }
83  
84 }

这篇文章也写的不错: http://my.oschina.net/20076678/blog/33392 

线程池shutdown()方法和shutdownNow()区别:

  • shutdown()  不允许添加新的任务,等池中所有的任务执行完毕之后再关闭线程池。
  • shutdownNow() 不允许添加新的任务。立刻关闭线程池。不管池中是否还存在正在运行的任务。关闭顺序是先尝试关闭当前正在运行的任务。然后返回待完成任务的清单。已经运行的任务则不返回。

一般创建线程池大小不会固定值。一般都是根据系统cpu、io、内存等等信息来计算出来的。一般情况下只要不"太大"或者"太小"就可以了。可以用Runtime.getRuntime().availableProcessors()来获取当前jvm中cpu数目然后乘以每个cpu处理任务数即可。

  1. public static boolean interrupted
    测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
  2. public boolean isInterrupted()
    测试线程是否已经中断。线程的中断状态不受该方法的影响。
  3. public void interrupt()
    中断线程。

    其中,interrupt方法是唯一能将中断状态设置为true的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics