JDK四种常见线程池及使用场景、两种提交任务的方法

2023-05-16

转载:JDK 提供的线程池及使用场景 - 编程猎人 (programminghunter.com)

目录

四种常见线程池

1.newFixedThreadPool

2.newSingleThreadExecutor

3.newCachedThreadPool

4.newScheduledThreadPool

两种提交任务的方法

execute

submit

如何合理地选择或者配置

总结


四种常见线程池

JDK 为我们内置了四种常见线程池的实现,均可以使用 Executors 工厂类创建。

1.newFixedThreadPool


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

可以看到,FixedThreadPool 的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。

此外 keepAliveTime 为 0,也就是多余的空余线程会被立即终止(由于这里没有多余线程,这个参数也没什么意义了)。

而这里选用的阻塞队列是 LinkedBlockingQueue,使用的是默认容量 Integer.MAX_VALUE,相当于没有上限。

因此这个线程池执行任务的流程如下:

  1. 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务

  2. 线程数等于核心线程数后,将任务加入阻塞队列

    • 由于队列容量非常大,可以一直加加加

  3. 执行完任务的线程反复去队列中取任务执行

FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

2.newSingleThreadExecutor


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

从参数可以看出来,SingleThreadExecutor 相当于特殊的 FixedThreadPool,它的执行流程如下:

  1. 线程池中没有线程时,新建一个线程执行任务

  2. 有一个线程以后,将任务加入阻塞队列,不停加加加

  3. 唯一的这一个线程不停地去队列里取任务执行

听起来很可怜的样子 - -。

SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。

3.newCachedThreadPool


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

可以看到,CachedThreadPool 没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有 60 秒,超过后就会被回收。

CachedThreadPool 使用的队列是 SynchronousQueue,这个队列的作用就是传递任务,并不会保存。

因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。

它的执行流程如下:

  1. 没有核心线程,直接向 SynchronousQueue 中提交任务

  2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个

  3. 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就拜拜

由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

4.newScheduledThreadPool


public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;  

ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor, 最多线程数为 Integer.MAX_VALUE ,使用 DelayedWorkQueue 作为任务队列。

ScheduledThreadPoolExecutor 添加任务和执行任务的机制与ThreadPoolExecutor 有所不同。

ScheduledThreadPoolExecutor 添加任务提供了另外两个方法:

  • scheduleAtFixedRate() :按某种速率周期执行

  • scheduleWithFixedDelay():在某个延迟后执行


public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0L)
        throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period),
                                      sequencer.getAndIncrement());
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                 TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (delay <= 0L)
        throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      -unit.toNanos(delay),
                                      sequencer.getAndIncrement());
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;  

可以看到,这两种方法都是创建了一个 ScheduledFutureTask 对象,调用 decorateTask() 方法转成 RunnableScheduledFuture 对象,然后添加到队列中

看下 ScheduledFutureTask 的主要属性:


private class ScheduledFutureTask<V>
        extends FutureTask<V> implements RunnableScheduledFuture<V> {
 
    //添加到队列中的顺序
    private final long sequenceNumber;
    //何时执行这个任务
    private volatile long time;
    //执行的间隔周期
    private final long period;
    //实际被添加到队列中的 task
    RunnableScheduledFuture<V> outerTask = this;
    //在 delay queue 中的索引,便于取消时快速查找
    int heapIndex;
    //...  

DelayQueue 中封装了一个优先级队列,这个队列会对队列中的 ScheduledFutureTask 进行排序,两个任务的执行 time 不同时,time 小的先执行;否则比较添加到队列中的顺序 sequenceNumber ,先提交的先执行。

ScheduledThreadPoolExecutor 的执行流程如下:

  1. 调用上面两个方法添加一个任务

  2. 线程池中的线程从 DelayQueue 中取任务

  3. 然后执行任务

具体执行任务的步骤也比较复杂:

  1. 线程从 DelayQueue 中获取 time 大于等于当前时间的 ScheduledFutureTask

    • DelayQueue.take()

  2. 执行完后修改这个 task 的 time 为下次被执行的时间

  3. 然后再把这个 task 放回队列中

    • DelayQueue.add()

ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

两种提交任务的方法

ExecutorService 提供了两种提交任务的方法:

execute

execute():提交不需要返回值的任务

submit():提交需要返回值的任务


  void execute(Runnable command)  

`` execute() 的参数是一个 Runnable,也没有返回值。因此提交后无法判断该任务是否被线程池执行成功。


ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
    @Override
    public void run() {
        //do something
    }
});  

submit


<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);  

submit() 有三种重载,参数可以是 Callable 也可以是 Runnable

同时它会返回一个 Funture 对象,通过它我们可以判断任务是否执行成功。

获得执行结果调用 Future.get() 方法,这个方法会阻塞当前线程直到任务完成。

提交一个 Callable 任务时,需要使用 FutureTask 包一层:


FutureTask futureTask = new FutureTask(new Callable<String>() {    //创建 Callable 任务
    @Override
    public String call() throws Exception {
        String result = "";
        //do something
        return result;
    }
});
Future<?> submit = executor.submit(futureTask);    //提交到线程池
try {
    Object result = submit.get();    //获取结果
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}  

如何合理地选择或者配置

了解 JDK 提供的几种线程池实现,在实际开发中如何选择呢?

根据任务类型决定。 前面已经介绍了,这里再小节一下:

  • CachedThreadPool 用于并发执行大量短期的小任务,或者是负载较轻的服务器。

  • FixedThreadPool 用于负载比较重的服务器,为了资源的合理利用,需要限制当前线程数量。

  • SingleThreadExecutor 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。

  • ScheduledThreadPoolExecutor 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景。

自定义线程池时,如果任务是 CPU 密集型(需要进行大量计算、处理),则应该配置尽量少的线程,比如 CPU 个数 + 1,这样可以避免出现每个线程都需要使用很长时间但是有太多线程争抢资源的情况; 如果任务是 IO密集型(主要时间都在 I/O,CPU 空闲时间比较多),则应该配置多一些线程,比如 CPU 数的两倍,这样可以更高地压榨 CPU。

为了错误避免创建过多线程导致系统奔溃,建议使用有界队列。因为它在无法添加更多任务时会拒绝任务,这样可以提前预警,避免影响整个系统。

执行时间、顺序有要求的话可以选择优先级队列,同时也要保证低优先级的任务有机会被执行。

总结

这篇文章简单介绍了 Java 中线程池的工作原理和一些常见线程池的使用,在实际开发中最好使用线程池来统一管理异步任务,而不是直接 new 一个线程执行任务。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JDK四种常见线程池及使用场景、两种提交任务的方法 的相关文章

  • tslib-1.4在I.MX6ULL开发板上电容屏不能触摸问题

    一 前言 在采用触摸屏的移动终端中 xff0c 触摸屏性能的调试是个重要问题之一 xff0c 因为电磁噪声的缘故 xff0c 触摸屏容易存在点击不准确 有抖动等问题 Tslib是一个开源的程序 xff0c 能够为触摸屏驱动获得的采样提供诸如
  • C++与QML混合编程

    一 前言 简单来说 xff0c 混合编程就是通过Qml高效便捷的构建UI界面 xff0c 而使用C 43 43 来实现业务逻辑和复杂算法 Qt集成了QML引擎和Qt元对象系统 xff0c 使得QML很容易从C 43 43 中得到扩展 xff
  • 卸载ROS功能包

    步骤方法 xff1a 1 首先卸载包 sudo apt get purge ros indigo 2 然后卸载依赖包 sudo apt get autoremove
  • 要点初见:通过ROS包nmea_navsat_driver读取GPS、北斗定位信息(C/C++)

    先前在树莓派上用C C 43 43 读取过GPS北斗双模定位模块 xff0c 但因为定位模块的若干条定位数据无法立刻读取 xff0c 需要用delay 延迟1到2秒的时间才能把所有定位数据都读取进程序 xff0c 又不想写多线程 xff0c
  • 自动驾驶-使用卡尔曼滤波算法定位和跟踪

    参加过科一的人都知道 xff0c 学车的第一步不是操控车辆而是遵守交规 xff0c 行车礼让 xff0c 确保安全 可见安全驾驶才是行车的第一原则 为了确保安全 xff0c 司机应该观察周围车辆和行人的位置 xff0c 保持安全距离 自动驾
  • ROS使用笔记

    文章目录 1 提取bag中固定topic或者固定时间段数据2 提取pcd数据3 记录数据4 service amp action5 roslaunch文件6 自定义消息7 from raw velodyne packets to velod
  • linux安装Android Studio

    linux安装Android Studio 1 先在https developer android google cn studio hl 61 zh cn下载源码安装包 2 安装64位所需要的库 2 1如果使用的是Ubuntu的话执行以下
  • OpenCV中Mat的初始化与赋值

    1 type数据类型 常量类型的命名规则为 xff1a CV 位数 43 数据类型 43 通道数 关系如下 xff1a C1 C2 C3 C4 CV 8U 0 8 16 24 CV 8S 1 9 17 25 CV 16U 2 10 18 2
  • 近十年的VI-SLAM算法综述与发展

    本文主要总结一下这几年工作中遇到过以及改进过相关VIO算法 1 背景介绍 一个完整的 SLAM simultaneous localization and mapping 框架包括传感器数据 前端 后端 回环检测与建图 xff0c 如图1所
  • 当前开源的SLAM方案汇总2021.02

    感谢SLAMer前辈们不断的拼搏与进取 xff0c 才有了现在的丰富的学习资料 xff01 以下是至今SLAM开源代码的资料汇总 xff0c 后续将会更新主流slam开源代码的注释版本 xff0c 希望对研究SLAM的同学们有帮助 PTAM
  • VSLAM算法中的数学基础知识详细了解

    学习SLAM经验告诉我 xff0c 入门SLAM一般只需要两种两个方面的条件 xff0c 一是要有扎实的数学基础 xff0c 二是要有强大的动手编程能力 xff0c 但是这两个条件对于刚入门的同学来说 xff0c 极具挑战性 学习SLAM的
  • 2021互联网大厂职级对应薪资一览表

    原文连接 xff1a https mp weixin qq com s nYNZjJJzrO0Sc5h2AEPnaQ 互联网大厂新入职员工各职级薪资对应表 xff08 技术线 xff09 图片数据来源 xff1a 知乎加 上面的表格不排除有
  • “undefined reference to“ 问题及解决方法 实例分析

    在实际编译代码的过程中 xff0c 我们经常会遇到 34 undefined reference to 34 的问题 xff0c 简单的可以轻易地解决 xff0c 但有些却隐藏得很深 xff0c 需要花费大量的时间去排查 工作中遇到了各色各
  • int 占几个字节

    4个字节或2个字节 xff0c 主要看操作系统 xff0c 和编译器有关 xff0c 一个int的大小是操作系统的一个字长 TC是16位系统程序 xff0c 所以int是16bit xff0c 也就是两个字节 在32位linux和32位或6
  • ORBSLAM3 VIO初始化

    按照规矩 xff0c 先讲一下ORBSLAM3中的初始化大致流程 根据ORB SLAM3的论文介绍 xff0c IMU的初始化方法是基于下面3点 xff1a 1 xff09 纯视觉SLAM可以提供很好的位姿估计 xff0c 所以可以用纯视觉
  • 不一样的静态初始化——OpenVins

    今天讲一下黄国权老师实验室开源的OpenVins工程中的IMU初始化 xff0c 一般VIO初始化分为两种 xff0c 一种是静态初始化 xff0c 一种是动态初始化 xff0c 而OpenVins则利用加速度的方差差异将运动分为两种状态
  • CMakeList.txt的指令以及实例介绍

    一 Cmake 简介 cmake xff08 Cross platform make xff09 是一个开源的跨平台自动化构建系统 xff0c 用来管理程序构建 xff0c 不依于特定的编译器 所谓的跨平台就是可以在Windows xff0
  • 语法错误( error: array bound is not an integer constant before ‘]’ token)

    include lt iostream gt include lt cstdio gt using namespace std int N 61 100 int a N N int main 编译出现了 Error array bound
  • 使用ADB出现了system/bin/sh: adb: not found&system/bin/sh: pull: not found错误

    在使用ADB传送文件的时候出现了system bin sh adb not found amp system bin sh pull not found错误 解决办法 xff1a 可能你在使用adb pull 之前你使用了adb shell
  • 回环检测之决策模型

    前面我们已经讲了如何描述场景 xff0c 让机器人尽可能的了解周围环境 xff0c 那么了解了之后 xff0c 如何判断出是回环的呢 xff1f 本节讨论如何建立决策模型来根据当前场景描述和地图信息识别出可能的闭环 合理的决策模型可有效提高

随机推荐

  • 回环检测之DBoW2

    前面我们已经讲了回环检测中用的一些方法 xff0c 今天主要介绍一下现在用的最多的词袋模型 DBoW2 这里就不在细讲回环检测的定义 xff0c 具体可以看看我的前面的博客 xff0c 而回环检测在SLAM中的作用可以从下面的图片中大致有一
  • 快速解决rosdep update一直不通过问题

    以前安装ROS的时候遇到rosdep update不通过 xff0c 需要很多次测试才能通过 xff0c 能通过完全靠运气 xff0c 也找了网上很多种方法 xff0c 比较麻烦 xff0c 今天这里说一下一位大神帅鱼提供的一个方法 xff
  • Matlab读取文本数据

    用Nastran的时候 xff0c 想把bdf文件里的节点坐标导出来 xff0c 但是坐标的格式很奇怪 xff0c 见下图 xff1a 会发现这种科学计数法中间没有字母E或e xff0c 直接用Matlab中的load函数读取的话 xff0
  • 为学弟学妹熬夜的一份零基础 C++ 开发学习路线

    大家好 xff0c 我是帅地 之前写过几篇学习路线的文章 前端开发学习路线 Java 后端开发学习路线 一般开发岗主流的就是 Java 后台开发 xff0c 前端开发以及 C 43 43 后台开发 xff0c 现在 Go 开发也是越来越多了
  • ars408_ros驱动问题

    0 ars408 ros驱动 mkdir folder mkdir folder src cd folder src git clone https github com sergiocasaspastor myrepository git
  • Tensorflow实战:LSTM原理及实现(详解)

    LSTM规避了标准RNN中梯度爆炸和梯度消失的问题 xff0c 所以会显得更好用 xff0c 学习速度更快 下图是最基本的LSTM单元连接起来的样子 上图为一层LSTM单元连接起来的样子 xff0c 在工业上 xff0c LSTM是可以像一
  • Gazebo版本升级7.0--->7.16

    GPU issues The GPU problems reported in this issue have been solved with this pull request for the gazebo7branch The Gaz
  • 机器人局部动态避障算法dwa解析

    简介 dwa算法全称叫动态窗口法 xff08 dynamic window approach xff09 xff0c 其算法过程主要分为仿真获取机器人的运动轨迹 对轨迹进行评价选择最优轨迹两个主要过程 xff0c 动态窗口表达的是仿真的运动
  • 应届生应该如何准备校招

    大家好 xff0c 我是羽峰 xff0c 今天要和大家分享的是应届生应该如何准备校招 xff0c 希望该文章对一些今年要找工作的朋友有一些帮助 还是老话 xff0c 我是羽峰 xff0c 希望我所分享的文章能为您及更多的朋友带来帮助 欢迎转
  • docker容器迁移到其他服务器

    以把旧服务器的mysql容器迁移到虚拟机上为例 1 旧服务器查看路径映射 首先查看mysql容器的路径映射 span class token function docker span inspect mysql 2 基本数据由旧服务器迁移到
  • 查看msg类型数组的长度

    msg类型数组的使用 需要说明的是std msgs中有些数组用法和C 43 43 标准库中不一样 比如Float32MultiArray xff0c 其定义如下 xff1a std msgs MultiArrayLayout layout
  • ROS 查看tf树

    rosrun rqt tf tree rqt tf tree
  • C++——构造函数后加冒号

    其实冒号后的内容是初始化成员列表 xff0c 一般有三种情况 xff1a 1 对含有对象成员的对象进行初始化 xff0c 例如 xff0c 类 line 有两个私有对象成员 startpoint endpoint line 的构造函数写成
  • Redis——在windows下设置密码

    设置密码有两种方式 1 命令行设置密码 运行cmd切换到redis根目录 xff0c 先启动服务端 gt redis server exe 另开一个cmd切换到redis根目录 xff0c 启动客户端 gt redis cli exe h
  • C++——函数声明省略形参名的用法注意

    函数声明时 xff0c 形参可以没有参数名 xff08 形参 xff09 xff0c 函数定义时 xff0c 一定要有参数名 xff08 形参 xff09 xff0c 函数调用时 xff0c 一定要有参数名 xff08 实参 xff09 i
  • Go——打印变量类型的两种方法

    1 使用reflect的TypeOf方法 模块是 xff1a 34 reflect 34 fmt Println reflect TypeOf var 切片类型的输出 2 使用Printf中的占位符 T 占位符 说明 举例 输出 T 相应值
  • Mybatis——foreach用法

    目录 1 foreach元素的属性 2 foreach时 xff0c collection属性值的三种情况 xff1a 3 代码示例 xff1a 3 1 collection属性值类型为List 使用 64 Param注解自定义keyNam
  • linux学习——(踩坑)语法错误: 未预期的文件结尾

    if eq 3 then if e 34 1 34 then miss1 1 elif e 34 2 34 a e 34 3 34 then miss2and3 1 2 3 elif e 34 2 34 a e 34 3 34 then m
  • SpringBoot使用RedisTemplate

    目录 1 Redis五种基础数据结构 2 SpringBoot连接Redis 1 引入依赖 2 配置redis连接 3 编写测试类 3 详解 RedisTemplate 的 API 1 常用数据操作 2 几种数据结构操作的具体用法 1 Re
  • JDK四种常见线程池及使用场景、两种提交任务的方法

    转载 xff1a JDK 提供的线程池及使用场景 编程猎人 programminghunter com 目录 四种常见线程池 1 newFixedThreadPool 2 newSingleThreadExecutor 3 newCache