关于高并发与多线程中的线程池

2023-11-20

关于高并发与多线程中的线程池

定义

线程是稀缺资源,它的创建与销毁是一个相对偏重且耗资源的操作,而Java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为避免资源过度消耗需要设法重用线程执行多个任务。线程池就是一个线程缓存,负责对线程进行统一分配、调优与监控。

什么时候使用线程池:

  • 单个任务处理时间比较短
  • 需要处理的任务数比较大

线程池的优势:

  • 重用存在的线程,减少线程创建,消亡的开销,提高性能
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性,可统一分配,调优和监控。

线程池的创建方式

JavaJUC包下的Executors 提供了创建线程池的四种方式及底层参数(四种线程的创建方式其底层都是使用ThreadPoolExecutor这个类,根据参数的不同来定义的,下面会给出相关参数的说明)

newCachedThreadPool

定义及作用

创建一个可根据需要创建新线程的线程池,如果线程池中有可用线程(可用指线程存在且空闲),如果没有则创建一个新的线程去执行,通常用于执行时间比较短的异步任务

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//此核心线程数corePoolSize为0,maximumPoolSize接近无限大
                                      60L, TimeUnit.SECONDS,//keepAliveTime存活时间为 60s,时间到自动回收
                                      new SynchronousQueue<Runnable>());//使用的是同步阻塞队列
    }

用法实例

//定义一个线程类
class RunnableThread implements Runnable{
    private int i=0;
    public RunnableThread(int i) {
        this.i = i;
    }
     public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"执行了第"+i+"个任务!");
        }
    }
//创建10个任务
   ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new RunnableThread(i));

        }

可以看到一秒后一下执行了10个任务

newScheduledThreadPool

定义及作用

创建一个支持周期执行,可做定时任务的线程池,主要用于周期性的执行任务的场景

  public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,//maximumPoolSize接近无限大,keepAliveTime存活时间为 0纳秒,线程不会被回收
              new DelayedWorkQueue());//采用延迟队列,提交的任务按照执行时间排序到队列中
    }

用法实例

  //schedule 是ScheduledExecutorService特有的方法
ScheduledExecutorService  executorService = Executors.newScheduledThreadPool(6);
            executorService.schedule(new RunnableThread(1),6L,TimeUnit.SECONDS);
            executorService.schedule(new RunnableThread(2),5L,TimeUnit.SECONDS);
            executorService.schedule(new RunnableThread(3),4L,TimeUnit.SECONDS);
            executorService.schedule(new RunnableThread(4),3L,TimeUnit.SECONDS);
            executorService.schedule(new RunnableThread(5),2L,TimeUnit.SECONDS);

在这里插入图片描述
可以看到按预先设置的延时去执行任务,需要做定时任务的可以使用此线程去实现

newFixedThreadPool

定义及作用

根据需要创建一个可固定线程数的线程池,任务较多时入队等待,适用于任务数固定,平稳的场景下,就是确定并发压力的情况下,去创建指定的线程数

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,//核心线程数等于最大线程数
                                      0L, TimeUnit.MILLISECONDS,//纳秒
                                      new LinkedBlockingQueue<Runnable>()); //阻塞队列,任务量来的时候无可用线程时,入队等待
    }

用法实例

   ExecutorService  executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 6; i++) {
            executorService.execute(new RunnableThread(i));
        }

可以看到第一秒三个线程执行一次,第2秒这三个线程又重复执行了,没有创建新的线程

newSingleThreadExector

定义及作用

创建一个只有单个线程的线程池,使用唯一工作线程去执行任务,保证任务的有序执行,适用于要求任务有序进行的情况下,和newFixedThreadPool定义一样,只是线程数只有一个

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

用法实例


   ExecutorService  executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new RunnableThread(i));
        }

可以看到任务是一秒钟执行一个,且是有序进行

以上是Executors所提供的的四种线程池的创建,从上清楚的看到其底层是使用ThreadPoolExecutor根据不同的参数来确定的。下面主要是以这个类来对线程池底层原理做分析。

ThreadPoolExecutor

//构造方法
public ThreadPoolExecutor(int corePoolSize,  //核心线程数
                              int maximumPoolSize,  //最大线程数
                              long keepAliveTime,  //存活时间
                              TimeUnit unit,  //时间单位
                              BlockingQueue<Runnable> workQueue,  //使用队列类型,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出
                              ThreadFactory threadFactory,  //就是创建线程的线程工,可以通过自己自定义的方式去创建一个线程
                              RejectedExecutionHandler handler)//是一种拒绝策略,我们可以在任务满了后,根据采用的策略拒绝执行某些任务,java提供了四种执行策略:
   			    // AbortPolicy:中断抛出异常
    			//DiscardPolicy:默默丢弃任务,不进行任何通知
    			//DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
    			//CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

线程的执行原理(结合源码来看的话会更明白)

在这里插入图片描述
在实际项目开发中也是推荐使用手动创建线程池的方式,而不用默认方式,关于这点在《阿里巴巴开发规范》中是这样描述的:

img
创建一个自定义线程池,根据并发量和具体的任务需求去创建线程池

 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 0L, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(10));
        for (int i = 0; i < 100; i++) {
            threadPoolExecutor.execute(new RunnableThread(i)
            );
        }

execute源码:关于源码这部分说明,后面会做补充

   int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) { //如果小于核心线程数的数往下执行
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) { //
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command); //采用拒绝策略

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

关于线程池的基本使用和原理就先到这了,欢迎各位指出不足,共同学习!!

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

关于高并发与多线程中的线程池 的相关文章

  • 插入最大日期(独立于数据库)

    在我的本地设置中 我使用一个简单的 H2 数据库 托管 解决方案将有另一个 类似但不相同 数据库 我需要将最大可能日期插入到日期时间列中 我尝试使用 Instant MAX 但是 这会导致列中出现 169104626 12 11 20 08
  • Java:迭代 Collection 的最佳方法(此处为 ArrayList)

    今天 当我看到一段我已经使用了数百次的代码时 我很高兴地开始编码 迭代集合 此处为 ArrayList 出于某种原因 我实际上查看了 Eclipse 的自动完成选项 这让我想知道 在什么情况下以下循环比其他循环更好使用 经典的数组索引循环
  • org.apache.sling.api.resource,version=[2.3,3) -- 无法解析

    您好 我无法访问我的项目内容 我已经上传了从 CQ 访问内容所需的所有包 我唯一能看到的是 org apache sling api resource version 2 3 3 无法解析 这是否是异常的原因 如果是 请告诉我如何解决 中Q
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • Java AES 128 加密方式与 openssl 不同

    我们遇到了一种奇怪的情况 即我们在 Java 中使用的加密方法会向 openssl 生成不同的输出 尽管它们在配置上看起来相同 使用相同的键和 IV 文本 敏捷的棕色狐狸跳过了懒狗 加密为 Base64 字符串 openssl A8cMRI
  • 运行具有外部依赖项的 Scala 脚本

    我在 Users joe scala lib 下有以下 jar commons codec 1 4 jar httpclient 4 1 1 jar httpcore 4 1 jar commons logging 1 1 1 jar ht
  • 按第一列排序二维数组,然后按第二列排序

    int arrs 1 100 11 22 1 11 2 12 Arrays sort arrs a b gt a 0 b 0 上面的数组已排序为 1 100 1 11 2 12 11 22 我希望它们按以下方式排序a 0 b 0 首先 如果
  • Java 文件上传速度非常慢

    我构建了一个小型服务 它从 Android 设备接收图像并将其保存到 Amazon S3 存储桶中 代码非常简单 但是速度非常慢 事情是这样的 public synchronized static Response postCommentP
  • 在 S3 中迭代对象时出现“ConnectionPoolTimeoutException”

    我已经使用 aws java API 一段时间了 没有遇到太多问题 目前我使用的是库 1 5 2 版本 当我使用以下代码迭代文件夹内的对象时 AmazonS3 s3 new AmazonS3Client new PropertiesCred
  • 很好地处理数据库约束错误

    再一次 它应该很简单 我的任务是在我们的应用程序的域对象中放置一个具有唯一约束的特定字段 这本身并不是一个很大的挑战 我刚刚做了以下事情 public class Location more fields Column unique tru
  • 编辑文件名在 JComboBox 中的显示方式,同时保持对文件的访问

    我对 Java 很陌生 对堆栈溢出也很陌生 我正在尝试利用 JMF API 创建一个用 Java 编码的简单媒体播放器 到目前为止 我已经能够设置一个简单的队列 播放列表来使用JComboBox called playListHolder
  • 欧洲中部时间 14 日 3 月 30 日星期五 00:00:00 至 日/月/年

    我尝试解析格式日期Fri Mar 30 00 00 00 CET 14至 日 月 年 这是我的代码 SimpleDateFormat formatter new SimpleDateFormat dd MM yyyy System out
  • 在 Spring 中重构这个的最佳方法?

    private final ExecutorService executorParsers Executors newFixedThreadPool 10 public void parse List
  • 如何在JSTL中调​​用java方法? [复制]

    这个问题在这里已经有答案了 这可能是重复的问题 我只想调用不是 getter 或 setter 方法的方法例如 xyz 类的 makeCall someObj stringvalue Java类 Class XYZ public Strin
  • java.lang.NumberFormatException: Invalid int: "3546504756",这个错误是什么意思?

    我正在创建一个 Android 应用程序 并且正在从文本文件中读取一些坐标 我在用着Integer parseInt xCoordinateStringFromFile 将 X 坐标转换为整数 Y 坐标的转换方法相同 当我运行该应用程序时
  • 我可以创建自定义 java.* 包吗?

    我可以创建一个与预定义包同名的自己的包吗在Java中 比如java lang 如果是这样 结果会怎样 这难道不能让我访问该包的受保护的成员 如果不是 是什么阻止我这样做 No java lang被禁止 安全管理器不允许 自定义 类java
  • 替换后增量

    我自己已经有一个问题了 但我想扩展它后增量示例 https stackoverflow com questions 51308967 post increment with example char a D int b 5 System o
  • HQL Hibernate 内连接

    我怎样才能在 Hibernate 中编写这个 SQL 查询 我想使用 Hibernate 来创建查询 而不是创建数据库 SELECT FROM Employee e INNER JOIN Team t ON e Id team t Id t
  • spring中如何使用jackson代替JdkSerializationRedisSerializer

    我在我的一个 Java 应用程序中使用 Redis 并且正在序列化要存储在 Redis 中的对象列表 但是 我注意到使用 RedisTemplate 会使用 JdkSerializationRedisSerializer 相反 我想使用 J
  • Trie 数据结构 - Java [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 是否有任何库或文档 链接提供了在 java 中实现 Trie 数据结构的更多信息 任何帮助都会很棒 Thanks 你可以阅读Java特里树

随机推荐

  • ios -Unity3D的EasyAR集成到已经有项目中。

    近期 在做AR这一块 用EasyAR集成到iOS端 由于现在到项目已经上线 下一版本要做一个AR功能 于是迫于需求需要 自己研究和翻阅读好多集成到资料 通过整理分出几个重要到模块 其中在这里指出Xcode9版本确实好坑 建议弃坑 该用稍微好
  • android studio agpbi error,Android Studio 3.1.1 打Jar包出现AGPBI异常解决

    今天 写好Demo兴致勃勃准备打个Jar包在Unity中测试下 不料 突然出现AGPBI这个异常 日志如下 AGPBI kind error text Program type already present android support
  • Android Recyclerview焦点变化问题导致下拉刷新视觉卡顿

    如题 最近做项目时偶然发现了一个Recyclerview嵌套Recycleview的问题 业务模块是订单列表 涉及到一个订单包含多个子订单的情况 所以考虑使用嵌套来展示页面 这一切都是正常的 没有任何问题 然而 随着业务的展开需要查看详情单
  • 自己写的PLC编程软件,和FANUC PMC功能基本保持一致

    自己写的PLC编程软件 和FANUC PMC功能基本保持一致 下载地址 免积分 链接 pan baidu com s 162 GcF7wh SNT3McATPPmg 提取码 1234 https download csdn net down
  • 基于ShuffleNetv2-YOLOv4模型的目标检测

    目录 1 引言 摘要 1 1 说明 1 2替换完成的工程请参考gitee 2 网络结构基础 2 1YOLOv3 2 1 YOLOv4算法 2 3 ShuffleNetv2 2 4 替换后的网络结构 3 实验结果 3 1实验环境配置及数据集介
  • 时间复杂度+常见复杂度解释

    前言 算法的效率 虽然计算机能快速的完成运算处理 但实际上 它也需要根据输入数据的大小和算法效率来消耗一定的处理器资源 要想编写出能高效运行的程序 我们就需要考虑到算法的效率 算法的效率主要由以下两个复杂度来评估 时间复杂度 评估执行程序所
  • Vue long精度丢失问题

    原因 vue前端对long类型的精度无法接收 javascript 的 Number 类型最大长度是17位 mysql 使用bigint 类型长度是20位 前端解决方法 在全局的网络请求 用了一个处理数据的插件 来转换 后端解决的方法 Co
  • 网络安全不可忽视!企业如何做好网络安全。

    随着互联网的高速发展 其面临的安全隐患也暴露无疑 比如网络攻击 黑客入侵等 都会严重影响到网络业务的运行 为此很多企业都绞尽脑汁寻找抵御各类网络安全隐患 国家也出台了相关的等保方案让企业能够免于后顾之忧 下面我们来说说目前网络安全的具体内容
  • android音视频!BAT大厂面试基础题集合,不吃透都对不起自己

    前言 现在已经进入招聘季节 本篇文章旨在分享知名互联网企业面试官面试方法和心得 希望通过本文的阅读能给程序员带来不一样的面试体验和感受 放松面试心态 积极备战 找到正确的学习路线 一 架构师专题 想要掌握复杂的技术 必须要理解其原理和架构
  • ROS学习笔记(二)文件系统

    ROS学习笔记 二 文件系统 开篇 ROS的文件系统结构 要学会建立一个ROS工程 首先要认识一个ROS工程 了解它们的组织架构 从根本上熟悉ROS项目的组织形式 了解各个文件的功能和作用 才能正确的进行开发和编程 本章的主要内容有 介绍c
  • 就现在!为元宇宙和Web3对互联网的改造做准备!

    欢迎来到Hubbleverse 关注我们 关注宇宙新鲜事 预计阅读时长 8分钟 本文仅代表作者个人观点 不代表平台意见 不构成投资建议 如今 互联网是各种不同的网站 应用程序和平台的集合 由于彼此分离 它们缺乏互操作性和数据可移植性 因此
  • ant design vue中menu组件递归渲染报错解决

    ant design vue中menu组件递归渲染报错 开始递归组件后打开页面后报错如下 解决如下 使 单 件 式递归 成菜单 Before v2 0 因组件内部会动态更改a sub menu的属性 如果拆分单文件 无法将属性挂载到a su
  • BI的需求调研的方法分类

    今天看到一篇文章 里面提到需求调研的几种思路 觉得分类有些道理 结合项目写一下 这种方法论在指导实践和体现专业两个方面都很实用 1 现有报表 这个是最常用的 使用这种方法注意区别报表目的 紧急度 和数据是否适合在BI实现 细节度 为什么以前
  • Oracle统计多张表的Count数的和

    需求描述 Table1 job1 job1 id name status other column 1 file1 process 2 file2 failed 3 file3 success Table2 job2 job2 id nam
  • 03:MYSQL----DQL,聚合函数

    目录 1 介绍 2 语法 3 聚合函数 4 DOL 语句练习 5 SQL执行顺序 1 介绍 数据查询语言 用来查询数据库中表的记录 2 语法 select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表
  • 手写 git hooks 脚本

    我们的 Git 仓库中包含了编译后的代码 所以每次修改了源码 都需要运行一下编译命令 然后把源码和编译后的代码一起提交到 Git 仓库 这个流程没什么问题 但是 人脑不是电脑 总会有疏忽的时候 经常会出现这样一种情况 修改了源码 却忘记了运
  • sql判断字段是否为null,是否为空串

    问题现象 今天在项目中思考了一个问题 如何在sql中判断一个字段是否为 null值 是否为 空串 呢 问题分析 需要注意的是 null值 和 空串 并不是同一个概念 null值 就是这个字段没有赋值 也就是java中常说的 null 而 空
  • 权重实现随机抽奖

    一般抽奖是怎么实现的 在实习期间学会了一种通用的写法 在这里记录一下 最近在学Golang语法基础 这里就用Golang来写 package main import fmt time math rand func main r rand N
  • 模态对话框与非模态对话的几种销毁方法与区别

    前几天发现自己的程序中使用非模态对话框 Debug版本有警告提示如下 Warning calling DestroyWindow in CWnd CWnd OnDestroy or PostNcDestroy in derived clas
  • 关于高并发与多线程中的线程池

    关于高并发与多线程中的线程池 定义 线程是稀缺资源 它的创建与销毁是一个相对偏重且耗资源的操作 而Java线程依赖于内核线程 创建线程需要进行操作系统状态切换 为避免资源过度消耗需要设法重用线程执行多个任务 线程池就是一个线程缓存 负责对线