ThreadPoolExecutor详解

简介: 详细介绍了ThreadPoolExecutor线程池的各种参数和各种使用场景以及线程池中线程的创建时刻和策略

Thread & ExecutorService & ThreadPoolExecutor 总览

ExecutorService

类方法

这里写图片描述

  1. shutdown
    允许已经提交的任务(尚未开始执行和已经开始执行的)继续执行
  2. shutdownNow
    尚未开始执行的任务不再执行,同时尝试终止正在执行的任务
  3. 无论是shutdown 还是shutdownNow,两个的执行都会阻止新的任务提交
  4. 一个ExecutorService一旦termination,表明没有正在执行的任务,没有等待执行的任务,也不会有新的任务可以被提交。
  5. 如果一个ExecutorService不再使用,应该调用shutdown方法来回收资源。
  6. submit方法(三个重载方法)

    • 返回的Future对象可以用来取消任务和等待任务执行完成
  7. invokeAny和invokeAll方法

    • 用户批量执行任务,
    • invokeAny:会阻塞当前线程,直到某个任务完成。并返回这个任务相关的Future对象
    • invokeAll:会阻塞当前线程,直到所有任务完成。
  8. isShutdown和isTerminated分别对应于两个状态:关闭状态,终结状态
  9. 两阶段shutdown

    1. 先执行shutdown方法
    2. 调用awaitTermination方法
    3. 再调用shutdownNow方法
void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
}

Thread

  1. interrupt方法

    如果执行a.interrupt方法后,如果a线程(注意是a线程,不是调用线程)抛出了InterruptedException异常,那么a的中断状态会被清除。如果不是抛出InterruptedException异常,那么a的中断状态都会被设置。
  2. interrupted方法

    执行a.interrupted方法会返回a线程的中断状态,同时会清除a线程的中断状态
  3. isInterrupted方法

    执行a.interrupted方法会返回a线程的中断状态,不会清除a线程的中断状态
    

ThreadPoolExecutor

core pool size 及 max pool size

一个新的任务提交哪些情况下回创建新的线程:
    1. 已创建的的线程数小于corePoolSize(即便有线程是空闲的)
    2. 已创建的线程数大于corePoolSize小于maxPoolSize,同时任务队列已经满的情况下,也会创建新的线程

可以动态改变这两个的值:setCorePoolSize  以及 setMaximumPoolSize

如果corePoolSize==maxinumPoolSize,那么则创建了一个固定大小的线程池

keep alive time

如果已创建的线程大于了corePoolSize,并且如果有线程的空闲时间大于了keepAliveTime,那么这些线程会被kill掉直到剩下corePoolSize个线程。

可以动态设置:setKeepAliveTime方法

默认情况下keep-alive策略只会针对已创建线程数大于corePoolSize的情况下

可以通过执行allowCoreThreadTimeOut(boolean)让keep-alive策略应用在已创建线程数小于corePoolSize的情况下。

BlockingQueue

1. 如果已创建线程数小于corePoolSize,那么会创建新的线程来执行当前提交的任务,而不是进入阻塞队列
2. 如果已创建线程数大于等于corePoolSize,会尝试先进入阻塞队列,如果进入失败(其实就是队列已满),则会在maxPoolSize条件下创建新的线程来执行当前提交的任务。如果不满足maxPoolSize条件,那么就会执行 拒绝执行策略(默认的拒绝执行策略见下)
3. 通常有三种入队列策略
    1. 直接传递给线程(Direct handoffs)
比如:SychronousQueue

感觉可以理解为这个入队列会总是失败,就相当于没有这个队列一样。这样就能在maxPoolSize条件下尽可能快的创建(或选择空闲的线程)来执行新提交的任务。

如果提交的任务有互相的依赖性,可以考虑使用这种队列。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
    2. 无界队列(Unbounded Queue)
比如:LinkedBlockingQueue

可以理解为如果有任务需要入队列,那么总会入队成功。
因此按照创建新线程的条件,理论上不会有超过corePoolSize个数的线程。也就是说理论上线程数最多为corePoolSize,因此maxPoolSize的设置也就显得没有意义了。

如果提交的任务互相间没有依赖性,可以考虑使用这种队列
    
    3. 有界队列(Bounded Queue)
比如:ArrayBlockingQueue

如果使用有限个maxPoolSize,那么使用这种队列可以防止资源的耗尽。

使用长队列和小的线程池,可以降低CPU使用率,降低系统资源的消耗,以及降低线程上下文切换的消耗,但是会导致低吞吐量。如果任务频繁的阻塞,系统可能会创建比允许的线程数多的线程。

使用短队列和大的线程池,可以提高CPU使用率,但也有可能导致吞吐量下降。

拒绝执行策略

我自己的叫法,实际上就是 RejectedExceptionHandler
这里写图片描述


1. ThreadPoolExecutor.AbortPolicy  
    抛出RejectedExecutionException异常
    
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

2. ThreadPoolExecutor.CallerRunsPolicy
    在调用线程上执行(哪个线程提交的任务就哪个线程执行)
    
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}
3. ThreadPoolExecutor.DiscardPolicy
    直接放弃
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

4. ThreadPoolExecutor.DiscardOldestPolicy
    放弃当前队列中第一个任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}

Finalization

一个在程序中不再被引用的线程池如果同时没有剩余的线程,那么这个线程池会被自动的shutdown.

因此如果你想即便在忘记执行shutdown方法的时候仍能正常关闭线程池,
那么建议设置一个有限的keepAliveTime(针对大于线程数大于corePoolSize的那些线程),同时也执行下 allowCoreThreadTimeOut(boolean) .


欢迎关注公众号

70

目录
相关文章
|
6月前
|
监控 Java 调度
Java线程池ThreadPoolExecutor初略探索
Java线程池ThreadPoolExecutor初略探索
|
4月前
|
监控 Java
ThreadPoolExecutor 介绍
ThreadPoolExecutor 介绍
45 0
|
4月前
|
Java
ThreadPoolExecutor 使用
ThreadPoolExecutor 使用
35 0
|
6月前
|
Java
线程池ThreadPoolExecutor总结
线程池ThreadPoolExecutor总结
|
7月前
|
缓存 搜索推荐 Java
线程池之ThreadPoolExecutor
线程池之ThreadPoolExecutor
75 0
|
机器学习/深度学习 消息中间件 存储
ThreadPoolExecutor解读
ThreadPoolExecutor解读
|
算法 安全 Java
深入理解ThreadPoolExecutor
深入理解ThreadPoolExecutor
深入理解ThreadPoolExecutor
|
存储 缓存 监控
ThreadPoolExecutor:线程池不允许使用Executors创建
ThreadPoolExecutor:线程池不允许使用Executors创建
384 0
ThreadPoolExecutor:线程池不允许使用Executors创建
|
存储 缓存 监控
线程池 ThreadPoolExecutor 详解
对于操作系统而言,创建一个线程的代价是十分昂贵的, 需要给它分配内存、列入调度,同时在线程切换时要执行内存换页,清空 CPU 缓存,切换回来时还要重新从内存中读取信息,破坏了数据的局部性。因此在并发编程中,当线程创建过多时,会影响程序性能,甚至引起程序崩溃。 而线程池属于池化管理模式,具有以下优点: 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的性能消耗。 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性:能够对线程进行统一分配、调优和监控。
220 0
|
网络协议 Java
Java并发:线程池详解(ThreadPoolExecutor)
Java并发:线程池详解(ThreadPoolExecutor)
173 0