多线程与高并发(15)——线程池详解(非源码层面)

news2025/1/13 15:32:03

通过之前的一篇文章,我们总结了Executor框架。而在Executor框架中,ThreadPoolExecutor 是最核心的类。
ThreadPoolExecutor 看字面意思,是线程池的执行器。我们本篇文章就基于ThreadPoolExecutor 这个类来展开总结线程池。
下篇文章会从源码的角度解析ThreadPoolExecutor原理。

一、ThreadPoolExecutor类分析

1、构造方法

构造方法源码如下:

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数,可同时运行的最大线程数
                              long keepAliveTime,//除核心线程数之外的,空闲下来的线程的存活时间
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue,//装任务的队列,存储等待执行的任务
                              ThreadFactory threadFactory,//线程工厂,可自定义一个产生线程的类
                              RejectedExecutionHandler handler) {//拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

我们做个详解:
corePoolSize : 核心线程数,就是可同时运行的最小数量,不会被销毁的线程数,空闲时不还给操作系统。
maximumPoolSize:最大线程数,任务有一个等待队列,当这个队列满了,会启动除核心线程之外的线程,而当前可启动的最大线程数就是这个参数值了。
workQueue:任务队列,如果新来了任务,先判断当前运行的线程数量是否达到核心线程数,没达到就执行,如果达到的话,新任务就会被存放在队列中。
keepAliveTime :当线程池中的线程数量大于核心线程数的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了这个时间才会被回收销毁;
unit:keepAliveTime的时间单位;
threadFactory:生产线程的类;
handler:拒绝策略,如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolExecutor会员拒绝策略。
拒绝策略有以下四种:

AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。(抛异常拒绝)
DiscardPolicy :不处理新任务,直接丢弃掉。(不处理)
DiscardOldestPolicy:丢掉最早的未处理的任务。(丢队列最前端的任务)
CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。(执行此任务)当最大池被填满时,此策略为我们提供可伸缩队列。

2、ThreadPoolExecutor使用

示例代码如下:

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        //创建任务
        Callable<String> callable = () -> Thread.currentThread().getName();
        //线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                10,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadPoolExecutor.CallerRunsPolicy());

        //提交并执行任务
        for (int i = 0; i < 10; i++) {
            Future<String> submit = executor.submit(callable);
            String s = submit.get(3, TimeUnit.SECONDS);
            System.out.println(s);
        }

        //关闭线程池
        executor.shutdown();

    }

运行结果如下:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5

二、常见线程池

1、FixedThreadPool(指定线程数)

其构造函数源码如下:

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

从源码可以看出,FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都被设置为我们自己传递的nThreads参数,所以,除核心线程外没有多余的线程。
《Java 并发编程的艺术》图片如下:
在这里插入图片描述
线程数小于nThreads时,来新任务就创建新线程,等于nThreads时,就加入队列等待,然后线程闲下来就立刻从队列中取任务执行。
为什么不推荐使用FixedThreadPool?
因为maximumPoolSize无效,而LinkedBlockingQueue队列的最大值是 Integer.MAX_VALUE,运行中的线程池会一直接受任务,直到队列满了还会接受,极端情况下会造成OOM。

2、SingleThreadExecutor (1个线程)

构造函数源码如下:

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

就核心线程和最大线程就是1。
《Java 并发编程的艺术》图片如下:
在这里插入图片描述
和FixedThreadPool一样,但是就1个线程,任务来了就加入队列等待。
为什么不推荐使用SingleThreadExecutor ?
和FixedThreadPool一样,而LinkedBlockingQueue队列的最大值是 Integer.MAX_VALUE,运行中的线程池会一直接受任务,直到队列满了还会接受,极端情况下会造成OOM。

3、CachedThreadPool (线程数最大化)

构造函数代码如下:

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

从代码可以看出,其没有核心线程,来一个任务就创建一个线程(如果之前的线程不闲下来的话),一直创建到Integer.MAX_VALUE为止。极端情况下,这样会导致耗尽 cpu 和内存资源。
《Java 并发编程的艺术》图片如下:
在这里插入图片描述
1.首先执行 SynchronousQueue.offer(Runnable task) 提交任务到任务队列。如果当前 maximumPool 中有闲线程正在执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行 offer 操作与空闲线程执行的 poll 操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,否则执行下面的步骤 2;(有空闲线程就执行任务)
2.当初始 maximumPool 为空,或者 maximumPool 中没有空闲线程时,将没有线程执行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤 1 将失败,此时 CachedThreadPool 会创建新线程执行任务,execute 方法执行完成;(没有空闲线程就创建线程执行任务)
为什么不推荐使用CachedThreadPool?
允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

4、ScheduledThreadPoolExecutor (定时执行)

主要用来在给定的延迟后运行任务,或者定期执行任务。
构造函数代码如下:

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

ScheduledThreadPoolExecutor 使用的任务队列 DelayQueue 封装了一个 PriorityQueue,PriorityQueue会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(ScheduledFutureTask 的 time变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(ScheduledFutureTask 的 squenceNumber 变量小的先执行)。

运行机制图片如下:
在这里插入图片描述
1、当调用 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate() 方法或者 scheduleWithFixedDelay() 方法时,会向 ScheduledThreadPoolExecutor 的 DelayQueue 添加一个实现了 RunnableScheduledFuture 接口的 ScheduledFutureTask 。
2、线程池中的线程从 DelayQueue 中获取 ScheduledFutureTask,然后执行任务。

为什么不推荐使用ScheduledThreadPoolExecutor ?
允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。而且在实际项目中应用较少,了解即可。

三、阿里开发手册

《阿里开发手册》华山版的并发编程这一节,有如下规定,参考:
在这里插入图片描述

四、线程池大小

1、上下文切换

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。
任务从保存到再加载的过程就是一次上下文切换。
简单的公式:
CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/117761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java之线程同步和线程之间的通信

线程同步的概念&#xff1a; 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也会带来访问冲突的问题&#xff1a; 举例&#xff1a; public class Runnable_test implements Runnable {//实现Runnable接口 private int ticknumbers10;Overridepublic void …

【C++】——数据类型(二)

文章目录2. 数据类型2.1 整型2.2 sizeof关键字2.3 浮点型2.3.1 浮点数表示——小数点表示法2.3.2 浮点数表示——E表示法2.3.3 浮点数类型2.4 字符型2.5 转义字符2.6 字符串型2.7 布尔类型bool2. 数据类型 2.1 整型 整数就是没有小数部分的数字&#xff0c;如2、98、 -5286 和…

【机器学习】模型评估与选择(实战)

模型评估与选择&#xff08;实战&#xff09; 目录一、准备工作&#xff08;设置 jupyter notebook 中的字体大小样式等&#xff09;二、数据集读取与查看三、交叉验证实验1、划分数据集并置乱2、设计交叉验证实验3、进行训练&#xff08;采用随机梯度下降分类器&#xff09;4、…

推荐一款数据可视化分析工具

当今时代数据分析的发展&#xff0c;导致数据可视化成为企业必不可少的一部分&#xff0c;进而市面上也就涌现出各种丰富多彩的工具。传统的Excel无疑是数据可视化工具的典型&#xff0c;我们平时经常使用Excel制作简单表格&#xff0c;简单、方便&#xff0c;但是复杂一点的可…

酒业崛起一支奇兵,009将自信走向全球

“天若不爱酒&#xff0c;酒星不在天。地若不爱酒&#xff0c;地应无酒泉。天地既爱酒&#xff0c;爱酒不愧天。”这是李白《月下独酌》中的句子&#xff0c;诗仙也是酒仙&#xff0c;已是广为人知的轶事。 中国是酒的国度&#xff0c;酒也是历史和文化的一种表达。正因为如此&…

【读书笔记】高级FPGA设计之面积结构设计

目录 面积结构设计 折叠流水线 基于控制的逻辑复用 资源共享 复位对面积的影响 无复位的资源 无置位的资源 无同步复位的资源 复位 RAM 利用置位/复位触发器引脚 总结 面积结构设计 本篇讨论数字设计的三个主要物理特性的第二个&#xff1a;面积。并分析在FPGA中结构…

【代码随想录】Day67哈希表:力扣242,383,1,349,202,454,15,18

目录 基础知识 哈希表 哈希函数 2.哈希碰撞 常见的哈希结构&#xff08;三种&#xff09; 数组 集合set 映射map 经典题目 数组作为哈希表 例题&#xff1a;力扣242 已完成 例题&#xff1a;力扣383 已完成 例题&#xff1a;力扣49 例题&#xff1a;力扣438 set…

小黑实习第二天,正在为hbase而头疼的leetcode之旅:671. 二叉树中第二小的节点

小黑代码(暴力) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def findSecondMinimumValue(self, root: …

执照吊销了能否恢复

一、执照吊销了能否恢复 &#xff11;、按照法律规定&#xff0c;企业法人被吊销营业执照&#xff0c;只是企业解散程序的开始。《公司法》规定&#xff0c;企业法人被吊销营业执照后应当依法进行清算&#xff0c;清算程序结束并办理工商注销登记后&#xff0c;该企业法人才归…

间隔分区表merge into报错“-2903: 语句块/包/存储函数中的间隔分区不支持自动扩展”

描述 版本&#xff1a; DM V8 --08134283904-20220804-166351-20005 Pack4 初始化参数&#xff1a; 默认 ini参数&#xff1a; 默认 执行间隔分区表上执行merge into语句报错&#xff0c;信息如下&#xff1a; 同样的语句&#xff0c;在Oracle中执行正常。 测试 创建环境&a…

Springboot利用redis缓存,结合Aop与自定义注解实现接口节流

接口的节流是开发过程中为了防止单一微服务模块突然遭受太多并发导致用户服务不流畅而产生的业务需求&#xff0c;就是实现在固定时间内访问同一个接口的次数也固定。开发过程中通常采用redis去作为缓存去快存快取&#xff0c;对于需求次数较多的数据可以存储在redis内部&#…

Ansible剧本使用

剧本语言 剧本使用的yaml语言 yaml文件的后缀为.yml或者.yaml 使用空格做为缩进 相同层级的元素左侧对齐即可 缩进时不允许使用 Tab 键&#xff0c;只允许使用空格 创建剧本 直接编辑不存在会自动创建这个文件&#xff0c;先用touch新建也行 vim juben.yml编写剧本 hosts&am…

C语言零基础项目:2D 赛车游戏,详细思路+源码分享

目录 一、简介 二、如何建立一个地图包 三、关于碰撞图的绘制 四、游戏时的说明 五、如何更好地绘制赛场图与碰撞图&#xff1f; 游戏截图 源码下载 一、简介 此游戏是《2D 赛车》的”魔改版“——2.5D 双人赛车&#xff01; 原作实现了 2D 视角的赛车游戏&#xff0c…

关于 国产麒麟系统赋值给双精度double时乘以1.0f编译器优化 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/128459376 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

【 uniapp - 黑马优购 | 首页】小程序首页全局配置(home、网络请求、轮播图、分类...)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

csdn里的KaTex 公式语法

KaTex 语法 Accents 字母各种上下标 波浪&#xff0c;箭头&#xff0c;声调等 Delimiters 分隔符 大括号&#xff0c;小括号&#xff0c;方括号 Environments 行列式里多行多列数字表达式包含 HTML Letters and Unicode 字符和 Unicode Layout 布局 Spacing 空格 Logic and Se…

Centos系统,防火墙没开,docker部署的rabbitmq不能外网访问监听端口,但别的端口都能正常访问???

真是一个神奇的问题&#xff0c;防火墙firewalld ,iptables都没开。 之前访问都正常&#xff0c;最近可能是服务器被动了。rabbitmq的相关监听接口&#xff0c;只能本机服务器连接了&#xff0c;导致设备连接不了rabbitmq组件了。 排查问题记录 1.防火墙是否开启。发现是关闭…

web仿真或实际内存分析应用及自动化方案

js 自带 GC&#xff08;垃圾回收&#xff09;机制&#xff0c;因此绝大多数 web 开发人员不会在日常开发中考虑内存情况&#xff08;包括本人&#xff09;&#xff0c;在多数业务场景中&#xff0c;这可能没有问题&#xff0c;但在一些核心web应用场景下&#xff08;比如某个页…

【Spring(一)】初识Spring(史上最详细的Spring介绍!)

文章目录前言1.初识Spring2.Spring Framework系统架构3.核心概念前言 在学习 Spring 之前&#xff0c;我们需要先知道为什么要学习它?    IT业的任何一门技术,它只有抢占了很强的市场占有率&#xff0c;才会有更多的人使用和学习它&#xff0c;Spring技术在我们Java开发界拥…

APP怎么免费接入MobPush

1、获取AppKey 申请Appkey的流程&#xff0c;请点击 http://bbs.mob.com/thread-8212-1-1.html?fromuid70819 2、下载SDK 下载解压后&#xff0c;如下图&#xff1a; 目录结构 &#xff08;1&#xff09;Sample&#xff1a;演示Demo。&#xff08;2&#xff09;SDK&#…