【多线程】线程池 详解

news2025/1/22 12:53:33

线程池 详解

  • 1. 线程池是什么
  • 2. 标准库中的线程池
  • 3. 实现线程池
  • 4. 面试题

1. 线程池是什么

虽然线程的创建和销毁的开销比较小, 但还是有的, 如果频繁的创建和销毁线程, 开销还是比较大的.解决: 线程池或者协程, 本文主讲线程池.

线程池: 把线程提前创建好, 放到池子里, 后面需要用到线程直接从池子里面取, 不必从系统申请, 
线程用完, 不是还给系统, 而是放到池子里面, 必备下次使用, 这样就减去了频繁创建销毁线程的开销.

举个栗子:

  • 一家快递店,店里没有雇人,而是每次有业务来了,就现场找一名同学过来把快递送了,然后解雇同学。这个类比我们平时来一个任务,起一个线程进行处理的模式。
  • 很快老板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。
  • 老板还是很善于变通的,于是决定最多雇10个人, 并且雇了 10 个人之后不再解雇, 当一个任务来时, 老板就看如果还没有雇到 10 个(就算已经雇的人是闲着的, 只要没雇到 10 个就会雇新人), 就雇 1 个人去送快递, 否则只是把业务放到一个本本上,等这 10 个快递人员有空闲的时候去处理。

这个就是我们要带出的线程池的模式。
线程池最大的好处就是减少每次启动、销毁线程的损耗。

为什么线程放到池子里面就比从系统申请释放更快 ?
这就涉及到操作系统的用户态与内核态:
在这里插入图片描述

自己写的代码运行在用户态, 有些代码需要通过调用操作系统的 API, 进一步的逻辑会在内核中执行.
比如 System.out.println, 本质上需要经过 write 系统调用进入到内核中, 在内核里面会执行一些逻辑, 在内核中运行的代码称为 “内核态” 运行的代码.

创建线程本身需要内核的支持, 创建线程本身就是在操作系统内核中创建 PCB, 调用 Thread.start 也要进入内核态运行.

而把创建好的线程放到 “池子里”, 由于 “池子” 是用户态实现的, 放到池子里/从池子里取这个过程不需要涉及到内核态, 用纯粹的用户代码就能完成, 一般认为纯用户态的操作效率比经过内核态处理的操作效率更高.
为什么 ?
不是说内核处理的效率一定真的低, 而是说代码进入内核态之后运行就不可控了, 你不知道此时内核背负了多少任务, 内核什么时候执行我们的任务, 什么时候运行完就不知道了, 有时快有时慢, 所以是不可控的.

2. 标准库中的线程池

  1. ThreadPoolExecutor
    构造方法:
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
  • corePoolSize: 核心线程数 (正式员工数量)
  • maximumPoolSize: 最大线程数 (正式员工 + 临时工数量)
  • keepAliveTime: 闲置的线程数超过corePoolSize时, 临时工多长时间后被销毁
  • unit: 时间的单位 (s, ms, us)
  • workQueue: 任务队列, 通过 submit 方法将任务放到任务队列中
  • threadFactory: 线程工厂, 线程是怎么创建出来的
  • handler: 拒绝策略, 任务队列满了怎么办? 1. 忽略最新任务 2. 阻塞等待 3. 丢弃最老的任务 4. 抛异常…
  1. Executors
    标准库中简化版本的线程池, 本质是针对 ThreadPoolExecutor 进行封装, 还提供一些默认参数.
  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
  • 返回值类型为 ExecutorService
  • 通过 ExecutorService.submit 可以注册一个任务到线程池中.
        ExecutorService pool = Executors.newFixedThreadPool(10);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });

Executors 创建线程池的几种方式:

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
  • newSingleThreadScheduledExecutor: 创建一个单个线程带有定时器的线程池.
  • newWorkStealingPool: 创建一个任务抢占式的线程池.每个线程都有自己的任务队列, 当把自己的任务全执行完了, 就去其他线程的任务队列中 “窃取” 任务进行执行.(在日常生活中的话就是指帮助别人干一些工作)

所以加上 ThreadPoolExecutor 共有 7 种创建线程池的方法

3. 实现线程池

  • 核心操作为 submit, 将任务加入线程池中
  • 使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.
  • 使用一个 BlockingQueue 组织所有的任务
  • 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.
  • 指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增线程了.
class MyThreadPool {
    //任务直接用Runnable,不用另外创建类了
    //用阻塞队列来组织任务
    private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();

    //用来描述线程的类
    static class Worker extends Thread{
        private BlockingQueue<Runnable> queue=null;
        //利用构造方法获取任务队列
        public Worker(BlockingQueue<Runnable> queue){
            this.queue=queue;
        }

        @Override
        public void run() {
            while(true){
                try {
                    //如果队列为空则阻塞
                    Runnable runnable=queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    //用数组来当作池存储线程
    List<Thread> list=new ArrayList<>();
    //构造方法中创建若干线程放到线程池中
    public MyThreadPool(int n){
        for(int i=0;i<n;i++){
            Worker worker=new Worker(this.queue);
            //注意不要忘记start
            worker.start();
            //加入线程池中
            list.add(worker);
        }
    }

    //提交任务
    public void submit(Runnable runnable)  {
        try {
            this.queue.put(runnable);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        MyThreadPool pool=new MyThreadPool(10);
        for(int i = 1;i <= 100;i++){
            int n = i;
            //submit了100次,相当于100个任务进入了任务队列,每个线程分一些,很快就执行完了
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("myThreadPool: " + n);
                }
            });
        }
    }
}

4. 面试题

有一个程序, 这个程序需要并发的完成一些任务, 如果使用线程池的话, 线程池中的线程数设置为多少合适 ?

回答:

  • 我们要通过性能测试, 才能找到合适的值.
  • 例如写一个服务器程序, 服务器通过线程池处理用户请求, 就可以针对这个服务器进行性能测试, 比如构造一些请求发送给服务器, 要进行性能测试的话, 请求就要构造很多, 比如 每秒发送 500/1000/2000 个, 可以根据实际业务场景构造一个合适的值
  • 然后根据线程池中不同的线程数, 观察任务的处理速度, 程序持有的 CPU 占有率, 一般线程数越多, 执行速度越快, 但是 CPU 占有率越高, 需要找到一个程序速度能接收且 CPU 占有率也合理的平衡点.

为什么不让 CPU 占有率 过高 ?

对于线上服务器, 一定要留有一定的冗余以便随时应对可能的突发情况, 例如请求暴涨, 若本身就把 CPU 快占完了, 这是突然来了一波请求高峰,此时服务器可能直接就挂了.

总结: 自己实现线程池

  1. 能够描述任务 (Runnable)
  2. 能够组织任务 (BlockingQueue)
  3. 能够描述工作线程
  4. 组织线程
  5. 线程从任务队列中取任务执行

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

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

相关文章

黑马 小兔鲜儿 uniapp 小程序开发- 分类模块- day04

黑马 小兔鲜儿 uniapp 小程序开发- 推荐模块- day03_软工菜鸡的博客-CSDN博客 本课程是全网首套用 vue3 加 TS 写的 uniapp 项目&#xff0c; 里面大量封装自己的组件库&#xff0c;课程从 uni-app 基础入手&#xff0c;按照9大电商业务模块逐步实现完整的电商购物流程业务&am…

技术人员应该使用那种搜索引擎?

built-in.o是Linux内核中的组件 下面是三种主流搜索引擎的搜索结果&#xff0c;请参考&#xff0c;一切尽在不言中。

CSS - 快速实现悬浮吸顶,当页面滑动一定距离时固定吸附在顶部(position: sticky)

效果图 如下图所示&#xff0c;利用 position: sticky 属性轻松实现。 示例代码 新建一个 *.html 文件&#xff0c;一键复制运行起来。 <body><section class"content"><div class"item">我是悬浮吸顶区域</div><h1>我是…

【leetcode 力扣刷题】栈和队列的基础知识 + 栈的经典应用—匹配

栈和队列的基础知识 栈的经典应用—匹配 栈和队列基础知识232. 用栈实现队列225. 用队列实现栈 20. 有效的括号1047. 删除字符串中的所有相邻重复项 栈和队列基础知识 数据结构课程介绍线性结构的时候&#xff0c;介绍有线性表、链表、栈和队列。线性表&#xff0c;比如array…

室内探索无人机,解决复杂环境下的任务挑战!

前言 室内探索无人机是一种专为在室内环境中进行任务的无人机系统。相比传统的人员部署&#xff0c;室内探索无人机具有更高的灵活性和机动性&#xff0c;能够在复杂的室内环境中执行任务&#xff0c;用于未知环境的探索和特定目标的搜索。 为完成无人机室内搜索与识别等复杂…

无缝数据传输:StreamSet安装部署的最佳实践

文章目录 概要安装方法尝试安装部署方案1. 下载datacollector镜像2. 启动容器3. 访问streamsets小结 概要 StreamSets是一款流数据集成平台&#xff0c;旨在帮助用户轻松地收集、处理和传输大规模数据流。它提供了直观的界面和强大的功能&#xff0c;可用于实时数据流的提取、…

无线测温系统在运行时怎么判断配电设备出现故障?

现如今&#xff0c;电力测温方面使用无线测温系统的使用范围越来越大&#xff0c;比较熟悉的人都了解&#xff0c;传统的温度测量方式周期长、施工复杂&#xff0c;效率低&#xff0c;不便于管理&#xff0c;发生故障时要耗费大量的人力物理排查和重新铺设线缆。而在特定场合下…

若依+lodop+jasperreports+ireport 设计打印票据格式(一)

若依lodopjasperreportsireport 设计打印票据格式 一、设计表格 官网下载ireport5.6&#xff0c;解压安装&#xff0c;jdk 1.7适配ireport5.6&#xff0c;jdk1.8不适配 安装好jdk1.7(不用配置path&#xff0c;安装好就行)&#xff0c;进入ireport5.6安装目录&#xff0c;找到…

异步FIFO设计的仿真与综合技术(2)

概述 本文主体翻译自C. E. Cummings and S. Design, “Simulation and Synthesis Techniques for Asynchronous FIFO Design 一文&#xff0c;添加了笔者的个人理解与注释&#xff0c;文中蓝色部分为笔者注或意译。前文链接&#xff1a;异步FIFO设计的仿真与综合技术&#xff0…

模态分析的概念。C++减振器设计。

一、说明 模态分析是工程和物理学中用于研究系统或结构动态特性的技术。它涉及分析系统的振动或振荡的自然模式以及相应的频率、阻尼系数和振型。 在模态分析中&#xff0c;所研究的系统通常表示为一组质量、刚度和阻尼元件&#xff08;在下面的文章中忽略了阻尼&#xff09;。…

ARTS 打卡 第一周,初试ARTS

前言 认识三掌柜的想必都知道&#xff0c;我持续创作技术博客已经有6年时间了&#xff0c;固定每个月发布不少于6篇博文。同时&#xff0c;自己作为一名热爱分享的开发者&#xff0c;像ARTS这样的活动自然少不了我。由于我是打算挤在一起分享&#xff0c;之前都是做了本地文档记…

大数据Flink(七十九):SQL 的容错(Checkpoint)

文章目录 SQL 的容错(Checkpoint) 一、Checkpoint介绍

华为云云耀云服务器L实例评测|云耀云服务器L实例部署SpaceHuggers网页小游戏

华为云云耀云服务器L实例评测&#xff5c;云云耀云服务器L实例部署SpaceHuggers网页小游戏 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、SpaceHuggers小游戏介绍2.1 SpaceHuggers简介2.2 SpaceHuggers游戏玩法 三、实践环境介绍3.1 本次…

在UOS/Deepin下安装 Python 3.11.5 图文详解

01 先把操作系统更新一下 在开始菜单中&#xff0c;找到“终端”&#xff0c;点击启动&#xff0c;并依次输入以下两条命令即可&#xff1a; sudo apt update sudo apt upgrade 特别说明&#xff1a;Uos/Deepin 系统&#xff0c;要先进入“开发者模式”才行。“ 设置 — 通用…

如何用Polygon ID来证明你不是机器人?

1. 引言 喜剧演员约翰穆拉尼在最近的一个单口相声特辑中说&#xff1a;“世界是由机器人管理的&#xff0c;我们一天中的大部分时间都在告诉他们&#xff0c;我们不是一个仅仅登录并查看自己东西的机器人。” 这种经历很常见&#xff0c;从乏味的&#xff08;“找到所有的停车…

软件设计师--考点小总结

成绩展示 在这里插入图片描述 口诀篇 普密网–【图算法,普利姆算法,适合密网,所以与边无光;另外一个图算法就是有关了】 D-AES, 56-128–【DES,AES是对称加密的,而56就是对称加密的算法位数(三重DES,是112,恰好是56倍数),128则是非对称的位数】 排序 快一样,堆占1,…

入行IC| 数字IC设计和验证选哪个好?

很多初入IC行业的新人不知道选择验证还是设计&#xff0c;下面IC修真院就从技能&#xff0c;门槛等方面来为大家分析一下。 数字前端设计工程师是什么&#xff1f; 集成电路设计&#xff08;Integrated Circuit&#xff0c;简称IC&#xff09;一般分为数字IC设计、模拟IC设计…

macOS - 使用VLC

文章目录 关于 VLC安装查看帮助流媒体 MRL 语法:URL 语法:主程序 (core)音频视频截图:窗口属性: 子画面屏幕显示&#xff08;OSD&#xff09;:字幕:覆盖:轨道设置:播放控制:默认设备:高级: 输入播放列表性能选项: 热键跳跃大小: 关于 VLC VLC media player VLC 是一款自由、开…

5个鲜有人知的爬虫技巧

几点鲜有人知的爬虫技巧 技巧一 换个角度&#xff0c;解锁新姿势 在爬取某些 web 网站的时候&#xff0c;被各种反爬弄得哭天喊地。 什么几把 css 字体加密&#xff0c;什么几把 js 的 MD5 等&#xff0c;各种乱七八糟的加密&#xff0c;什么各种飞的验证 这时候&#xff0…

【汇编】计算机系统组成

【汇编】计算机系统组成 文章目录 【汇编】计算机系统组成冯诺依曼结构1. 总线2. 程序存储3. 存储器3.1地址线与字节3.2 读写逻辑 冯诺依曼结构 冯诺伊曼结构&#xff08;Von Neumann Architecture&#xff09;&#xff0c;又称存储程序计算机结构&#xff0c;是计算机体系结构…