一、ThreadPoolExecutor 参数说明

    public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • corePoolSize:核心线程池的大小。当提交一个任务到线程池时,核心线程池会创建一个核心线程来执行任务,即使其他核心线程能够执行新任务也会创建线程,等到需要执行的任务数大于核心线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法,核心线程池会提前创建并启动所有核心线程。

  • workQueue:任务队列。当核心线程池中没有线程时,所提交的任务会被暂存在队列中。Java 提供了多种阻塞队列

  • maximumPoolSize:线程池允许创建的最大线程数。如果队列也满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的空闲线程执行任务。值得注意的是,如果使用了无界的任务队列则这个参数不起作用。

  • keepAliveTime:当线程池中的线程数大于 corePoolSize 时,keepAliveTime 为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。值得注意的是,如果使用了无界的任务队列则这个参数不起作用。

  • TimeUnit:线程活动保持时间的单位。

  • threadFactory:创建线程的工厂。可以通过线程工厂给每个创建出来的线程设置符合业务的名字。

    // 依赖 guava
    new ThreadFactoryBuilder().setNameFormat("xx-task-%d").build();
  • handler:饱和策略。当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。Java 提供了以下4种策略:

    • AbortPolicy:默认。直接抛出异常。
    • CallerRunsPolicy:只用调用者所在线程来运行任务。
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    • DiscardPolicy:不处理,丢弃掉。

tips: 一般我们称核心线程池中的线程为核心线程,这部分线程不会被回收;超过任务队列后,创建的线程为空闲线程,这部分线程会被回收(回收时间即 keepAliveTime)

二、常见的 ThreadPoolExecutor 介绍

Executors 是创建 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 的工厂类。

Java 提供了多种类型的 ThreadPoolExecutor,比较常见的有 FixedThreadPool、SingleThreadExecutor、CachedThreadPool等。

FixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

FixedThreadPool 被称为可重用固定线程数的线程池。可以看到 corePoolSize 和 maximumPoolSize 都被设置成了 nThreads;keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止;使用了阻塞队列 LinkedBlockingQueue 作为线程的工作队列(队列的容量为 Integer.MAX_VALUE)。

FixedThreadPool 所存在的问题是,由于队列的容量为 Integer.MAX_VALUE,基本可以认为是无界的,所以 maximumPoolSize 和 keepAliveTime 参数都不会生效,饱和拒绝策略也不会执行,会造成任务大量堆积在阻塞队列中。

FixedThreadPool 适用于为了满足资源管理的需求,而需要限制线程数量的应用场景。

SingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

SingleThreadExecutor 是使用单个线程的线程池。可以看到 corePoolSize 和 maximumPoolSize 被设置为1,其他参数与 FixedThreadPool 相同,所以所带来的风险也和 FixedThreadPool 一致,就不赘述了。

SingleThreadExecutor 适用于需要保证顺序的执行各个任务。

CachedThreadPool

    public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

CachedThreadPool 是一个会根据需要创建新线程的线程池。可以看到 corePoolSize 被设置为 0,所以创建的线程都为空闲线程;maximumPoolSize 被设置为 Integer.MAX_VALUE(基本可认为无界),意味着可以创建无限数量的空闲线程;keepAliveTime 设置为60L,意味着空闲线程等待新任务的最长时间为60秒;使用没有容量的 SynchronousQueue 作为线程池的工作队列。

CachedThreadPool 所存在的问题是, 如果主线程提交任务的速度高于maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。

CachedThreadPool 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。

三、自建 ThreadPoolExecutor 线程池

鉴于上面提到的风险,我们更提倡使用 ThreadPoolExecutor 去创建线程池,而不用 Executors 工厂去创建。

以下是一个 ThreadPoolExecutor 创建线程池的 Demo 实例:

public class Pool {

    static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("pool-task-%d").build();
static ExecutorService executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() * 2,
200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024),
threadFactory, new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 无返回值的任务执行 -> Runnable
executor.execute(() -> System.out.println("Hello World"));
// 2. 有返回值的任务执行 -> Callable
Future<String> future = executor.submit(() -> "Hello World");
// get 方法会阻塞线程执行等待返回结果
String result = future.get();
System.out.println(result); // 3. 监控线程池
monitor(); // 4. 关闭线程池
shutdownAndAwaitTermination(); monitor();
} private static void monitor() {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
System.out.println("【线程池任务】线程池中曾经创建过的最大线程数:" + threadPoolExecutor.getLargestPoolSize());
System.out.println("【线程池任务】线程池中线程数:" + threadPoolExecutor.getPoolSize());
System.out.println("【线程池任务】线程池中活动的线程数:" + threadPoolExecutor.getActiveCount());
System.out.println("【线程池任务】队列中等待执行的任务数:" + threadPoolExecutor.getQueue().size());
System.out.println("【线程池任务】线程池已执行完任务数:" + threadPoolExecutor.getCompletedTaskCount());
} /**
* 关闭线程池
* 1. shutdown、shutdownNow 的原理都是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程。
* 2. shutdownNow:将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
* 3. shutdown:将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
*/
private static void shutdownAndAwaitTermination() {
// 禁止提交新任务
executor.shutdown();
try {
// 等待现有任务终止
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
// 取消当前正在执行的任务
executor.shutdownNow();
// 等待一段时间让任务响应被取消
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
// 如果当前线程也中断,则取消
executor.shutdownNow();
// 保留中断状态
Thread.currentThread().interrupt();
}
}
}

创建线程池需要注意以下几点:

  1. CPU 密集型任务应配置尽可能小的线程,如配置 Ncpu+1 个线程。
  2. IO 密集型任务(数据库读写等)应配置尽可能多的线程,如配置 Ncpu*2 个线程。
  3. 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。
  4. 建议使用有界队列。可以避免创建数量非常多的线程,甚至拖垮系统。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点儿,比如几千。

四、ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。

    public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory);
}

ScheduledThreadPoolExecutor 的功能与 Timer 类似,但功能更强大、更灵活。Timer 对应的是单个后台线程,而ScheduledThreadPoolExecutor 可以在构造函数中指定多个对应的后台线程数。

Java 提供了多种类型的 ScheduledThreadPoolExecutor ,可以通过 Executors 创建,比较常见的有 ScheduledThreadPool、SingleThreadScheduledExecutor 等。适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程数量的应用场景。

public class ScheduleTaskTest {

    static ThreadFactory threadFactory = new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").build();
static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5, threadFactory); public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 延迟 3 秒后执行 Runnable 方法
scheduledExecutorService.schedule(() -> System.out.println("Hello World"), 3000, TimeUnit.MILLISECONDS); // 2. 延迟 3 秒后执行 Callable 方法
ScheduledFuture<String> scheduledFuture = scheduledExecutorService.schedule(() -> "Hello ScheduledFuture", 3000, TimeUnit.MILLISECONDS);
System.out.println(scheduledFuture.get()); // 3. 延迟 1 秒后开始每隔 3 秒周期执行。
// 如果中间任务遇到异常,则禁止后续执行。
// 固定的频率来执行某项任务,它不受任务执行时间的影响。到时间,就执行。
scheduledExecutorService.scheduleAtFixedRate(() -> System.out.println("Hello ScheduleAtFixedRate"), 1, 3000, TimeUnit.MILLISECONDS); // 4. 延迟 1 秒后,每个任务结束延迟 3 秒后再执行下个任务。
// 如果中间任务遇到异常,则禁止后续执行。
// 受任务执行时间的影响,等待任务执行结束后才开始计算延迟。
scheduledExecutorService.scheduleWithFixedDelay(() -> System.out.println("Hello ScheduleWithFixedDelay"), 1, 3000, TimeUnit.MILLISECONDS);
}
}

ScheduledThreadPoolExecutor 的执行步骤大抵如下:

  1. 当调用 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate() 方法或者 scheduleWithFixedDelay()方 法时,会向 DelayedWorkQueue 队列添加 ScheduledFutureTask 任务。
  2. 线程池中的线程从 DelayedWorkQueue队列中获取执行时间已到达的 ScheduledFutureTask,然后执行任务。
  3. 线程修改 ScheduledFutureTask 任务的执行时间为下次将要被执行的时间。
  4. 线程把修改后的 ScheduledFutureTask 重新放回队列。

多线程编程学习十一(ThreadPoolExecutor 详解).的更多相关文章

  1. JavaEE开发之Spring中的多线程编程以及任务定时器详解

    上篇博客我们详细的聊了Spring中的事件的发送和监听,也就是常说的广播或者通知一类的东西,详情请移步于<JavaEE开发之Spring中的事件发送与监听以及使用@Profile进行环境切换&g ...

  2. 【Java多线程】Executor框架的详解

    在Java中,使用线程来异步执行任务.Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源.同时,为每一个任务创建一个新线程来执行 ...

  3. 从51跳cortex-m0学习2——程序详解

    跳cortex-m0——思想转变>之后又一入门级文章,在此不敢请老鸟们过目.不过要是老鸟们低头瞅了一眼,发现错误,还请教育之,那更是感激不尽.与Cortex在某些操作方式上的异同,让自己对Cor ...

  4. 多线程编程学习笔记——异步调用WCF服务

    接上文 多线程编程学习笔记——使用异步IO 接上文 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端 接上文 多线程编程学习笔记——异步操作数据库 本示例描述了如何创建一个WCF服务,并宿主 ...

  5. Linux Shell编程与编辑器使用详解

    <Linux Shell编程与编辑器使用详解> 基本信息 作者: 刘丽霞 杨宇 出版社:电子工业出版社 ISBN:9787121207174 上架时间:2013-7-22 出版日期:201 ...

  6. iOS学习之UINavigationController详解与使用(一)添加UIBarButtonItem

    http://blog.csdn.net/totogo2010/article/details/7681879 1.UINavigationController导航控制器如何使用 UINavigati ...

  7. [转]iOS学习之UINavigationController详解与使用(三)ToolBar

    转载地址:http://blog.csdn.net/totogo2010/article/details/7682641 iOS学习之UINavigationController详解与使用(二)页面切 ...

  8. [转]iOS学习之UINavigationController详解与使用(二)页面切换和segmentedController

    转载地址:http://blog.csdn.net/totogo2010/article/details/7682433 iOS学习之UINavigationController详解与使用(一)添加U ...

  9. 网络编程socket基本API详解(转)

    网络编程socket基本API详解   socket socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信. socket ...

随机推荐

  1. 《机器学习基石》---VC维

    1 VC维的定义 VC维其实就是第一个break point的之前的样本容量.标准定义是:对一个假设空间,如果存在N个样本能够被假设空间中的h按所有可能的2的N次方种形式分开,则称该假设空间能够把N个 ...

  2. spring boot 打 war包

    spring boot .spring cloud打 war包,并发布到tomcat中运行 1.pom文件修改 <packaging>war</packaging> 2.< ...

  3. ggplot2: how to check the color and coreponding value pairs

    The way to check the color and coreponding value pairs in ggplot2 To see what colors are used to mak ...

  4. js-EventLoop

    1.浏览器事件环 eventLoop是由js的宿主环境(浏览器)来实现的 事件循环可以简单的描述为以下四个步骤 1.函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步 ...

  5. 完美解决迅雷极速版强制升级到迅雷X

    虽然迅雷已死,但是还是软件还是有点点用的.废话不好多说,直接上解决办法: 1. 找到桌面的迅雷图标,右键选择打开文件位置; 2. 根据路径找到: 相对路径:Thunder Network\Thunde ...

  6. Go最火的Gin框架简单入门

    Gin 介绍 Gin 是一个 Golang 写的 web 框架,具有高性能的优点,,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务.官方地址:htt ...

  7. NN入门,手把手教你用Numpy手撕NN(一)

    前言 这是一篇包含极少数学推导的NN入门文章 大概从今年4月份起就想着学一学NN,但是无奈平时时间不多,而且空闲时间都拿去做比赛或是看动漫去了,所以一拖再拖,直到这8月份才正式开始NN的学习. 这篇文 ...

  8. JavaScript 数据结构与算法之美 - 线性表(数组、栈、队列、链表)

    前言 基础知识就像是一座大楼的地基,它决定了我们的技术高度. 我们应该多掌握一些可移值的技术或者再过十几年应该都不会过时的技术,数据结构与算法就是其中之一. 栈.队列.链表.堆 是数据结构与算法中的基 ...

  9. egret之每日登陆奖励

    //*******首登奖励********* */ //*********************** */ public setUserSetting(key, value) { if (value ...

  10. 2020届京东秋招正式批一面记录-Java开发-2019.08.31

    京东一面总结 总共时间持续时间约40分钟 1.你用过集合类里面哪些是线程安全的,哪些是线程不安全的?分别举两个例子? 线程安全:HashTable以及ConcurrentHashMap 非线程安全:A ...