线程池的使用及实现

使用多进程进行并发编程,会频繁的创建销毁进程,效率比较慢,所以引入了线程,线程使用复用资源的方式提高了创建销毁的效率,但是随着创建线程的频率进一步提高,开销仍然无法忽略不计了。

要想办法优化此处线程的创建销毁效率,方案有两种:

  1. 引入轻量级线程——纤程/协程。即Java 21里引入的”虚拟线程“。协程的本质是程序员在用户态代码中进行调度,不是靠内核的调度器调度的,节省了很多调度上的开销。
  2. 线程池。把要使用的线程提前创建好,用完了不销毁等待下次使用。每次创建一个新线程需要为该线程分配堆栈内存、初始化线程管理数据结构等等,这些操作都需要消耗一定的系统资源。使用线程池可以重复利用已经创建的线程,避免了这种开销。

1. Java标准库中的线程池

1.1 ThreadPoolExecutor类

 ThreadPoolExecutor 类提供了如下四个构造方法:

我们重点理解最后一个:

  1. corePoolSize(核心线程数):这是线程池中始终保持活动状态的线程数量。即使没有任务需要执行,这些线程也会保持活跃,可以理解为最小线程数。
  2. maximumPoolSize(最大线程数):最大线程池大小。当提交的任务数大于核心线程池大小并且工作队列已满时,线程池会创建新线程来处理任务,但新线程数量不会超过最大线程池大小。
  3. keepAliveTime(非核心线程空闲时间):非核心线程空闲时间。当线程池中的线程数量大于核心线程池大小时,如果线程空闲时间超过了该参数所指定的时间,那么这个线程就会被销毁,直到线程数量等于核心线程池大小。
  4. unit(时间单位):keepAliveTime参数的时间单位,可以是秒、毫秒等。
  5. workQueue(工作队列):任务队列。用于存储尚未执行的任务。线程池会从任务队列中取出任务并进行处理。
  6. threadFactory(线程工厂):用于创建新线程的工厂。可以通过自定义线程工厂来设置线程的名称、优先级等属性。
  7. handler(拒绝策略):当线程池无法处理新提交的任务时,将使用此策略来处理。常见的拒绝策略有:
    AbortPolicy:直接抛出异常,不处理任务。
    CallerRunsPolicy:只用调用者所在的线程来运行任务。
    DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试重新提交当前任务。
    DiscardPolicy:直接丢弃任务,不处理。

需要注意的是,corePoolSize和maximumPoolSize参数决定了线程池的容量大小,而workQueue则决定了能够存储多少个等待执行的任务。如果任务量过大,超出了workQueue的容量,再加上全部的线程都在执行任务的情况下,那么就会触发线程池的拒绝策略来处理这些任务,从而保证线程池不会因为资源被耗尽而崩溃。 

解释:

工厂模式:工厂模式是一种常见的设计模式,通过专门的  工厂类 / 工厂对象 来创建指定的对象,例如这里的 ThreadFactory。

工厂模式本质上是为了给Java语法填坑的,举个例子:

我要表示平面上的一个点,可以用笛卡尔坐标系,也可以用极坐标系:

 

 很明显,这样的代码无法通过编译,因为,这两个构造方法无法构成重载。

 为了解决上述问题,就引入了工厂模式,使用普通的方法来创建对象,就是把构造方法封装了一层:

此时这两个方法就叫工厂方法,如果把工厂方法放到其他的类里,这个类就叫工厂类,总的来说,通过静态方法封装new 操作,在方法内部设定不同的属性完成对象初始化,构造对象的过程就是工厂模式。

1.2 Executors 工厂类

ThreadPoolExecutor 类本身用起来比较复杂,所以标志库中还提供了另一个版本,把ThreadPoolExecutor 给封装了一下。即 Executors 工厂类,通过这个类来创建出不同的线程池对象(在内部把ThreadPoolExecutor 创建好了,并设置了不同的参数)。

 

SingleThreadExecutor:只包含单个线程的线程池
ScheduledThreadPool:定时器类似物,能延时执行任务
CachedThreadPool:线程数目能动态扩容
FixedThreadPool:线程数目固定

使用示例:

public class ThreadDemo28 {
    public static void main(String[] args) {
        //创建一个四个线程的线程池
        ExecutorService service = Executors.newFixedThreadPool(4);
        //通过submit方法添加任务
        service.submit(() -> {
            System.out.println("Ting");
        });
    }
}

ThreaPoolExecutor  也是通过submit 添加任务,只是构造方法不同。

希望高度定制化时使用 ThreadPoolExecutor 

 创建线程池的时候,怎么设置线程池的线程数量比较合适?

这个情况需要具体问题具体分析:

一个线程是CPU密集型任务,还是 IO 密集型任务

CPU密集型任务:这个线程大部分都在CPU上执行,如果所有线程都是CPU密集型的,这个时候建议线程数量不要大于设备的逻辑核心数量。

IO 密集型任务:这个线程大部分时间都在等待 IO ,如果所有线程都是 IO 密集型的,这个时候线程数量可以很多

上述两种情况是极端情况,大部分情况都是,有一部分线程是 CPU 密集型,一部分是 IO 密集型,所以,更适合的做法是通过测试的方式找到合适的线程数目。 

即尝试给线程池设定不同的线程数目分别进行性能测试,对比每种线程数目下,总的时间开销,和系统资源占用的 开销,找到一个最合适的值。

 

2. 简单实现一个线程池

 我们先来整理一下,实现一个线程池需要哪些内容:

  • 一个任务队列:记录要执行的任务
  • submit方法:添加任务
  • 构造方法:指定线程的数量以及创建,运行线程

 

class MyTreadPoolExecutor {
    //任务队列,这里使用一个阻塞队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    //构造方法
    public MyTreadPoolExecutor(int num) {
        for(int i = 0; i < num; i++) {
            //创建线程
            Thread t = new Thread(() -> {
                //循环取出任务并执行
                while(true) {
                    try {
                        queue.take().run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
        }
    }

    //submit
    public void submit(Runnable runnable) {
        //添加任务
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
public class TreadDemo29 {
    public static void main(String[] args) throws InterruptedException {
        MyTreadPoolExecutor pool = new MyTreadPoolExecutor(4);

        for(int i = 0; i < 1000; i++) {
            int n = i;
            pool.submit(() -> {
                System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务:" + n);
            });
        }
    }
}

运行效果:

注意这里的任务执行无序的原因是,多个线程并发执行。

例如:任务 0 刚被某个线程拿到,改线程就被调度出了cpu 此次任务 2 就可能被拿到并执行了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解单链表OJ题

链表OJ经典题目 一.删除链表中等于给定值 val 的所有结点leetcode链接 二.给定一个带有头结点 head 的非空单链表&#xff0c;返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点leetcode链接 三.反转一个单链表leetcode链接 四.输入一个链表&#xff0c…

HarmonyOS学习 第1节 DevEco Studio配置

俗话说的好&#xff0c;工欲善其事,必先利其器。我们先下载官方的开发工具DevEco Studio. 下载完成后&#xff0c;进行安装。 双击DevEco Studio&#xff0c;点击Next按照指引完成安装 重新启动DevEco&#xff0c;点击 Agree 进入环境配置&#xff0c;安装Node.js和ohpm 点击Ne…

蓝牙物联网对接技术难点有哪些?

#物联网# 蓝牙物联网对接技术难点主要包括以下几个方面&#xff1a; 1、设备兼容性&#xff1a;蓝牙技术有多种版本和规格&#xff0c;如蓝牙4.0、蓝牙5.0等&#xff0c;不同版本之间的兼容性可能存在问题。同时&#xff0c;不同厂商生产的蓝牙设备也可能存在兼容性问题。 2、…

STM32 HAL库代码编程风格--STM32外设结构体代码风格

1.HAL库代码风格理解 I/O、UART、SPI、USB、IIC等外设结构体总结 1、GPIO外设 GPIO只有初始化结构体。只需要定义初始化结构体即可(GPIO_InitTypeDef GPIO_InitStruct;)&#xff0c;内部成员都可通过初始化结构体引用。 2、UART外设 UART、IIC、SPI等外设&#xff08;除I/O外…

windows系统nodeJs报错node-sass npm ERR! command failed

报错信息 npm WARN deprecated request2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 npm WARN deprecated tar2.2.2: This version of tar is no longer supported, and will not receive security updates. Please upgrade asa…

英文作文AI创作工具,怎么用AI批量写英文作文

在当今全球化的时代&#xff0c;良好的英语写作能力不仅仅是学业成功的关键&#xff0c;更是职场竞争中的重要技能。随着科技的不断发展&#xff0c;我们现在有了更多的工具可以辅助我们提升英语写作水平。在这篇文章中&#xff0c;我将为大家推荐5种强大的AI写作工具&#xff…

C++基础 -42- STL库之list链表

———————STL库之list链表——————— &#x1f384; list链表的格式(需要定义头文件) list<int> data1(4, 100);list<int> data2(4, 500);&#x1f384;list链表的合并接口 &#x1f384;举例使用合并接口并且验证 data2.merge(data1);list<int>::…

探索低代码的潜力、挑战与未来展望

低代码开发作为一种新兴的开发方式&#xff0c;正在逐渐改变着传统的编程模式&#xff0c;低代码使得开发者无需编写大量的代码即可快速构建各种应用程序。然而&#xff0c;低代码也引发了一系列争议&#xff0c;有人称赞其为提升效率的利器&#xff0c;也有人担忧其可能带来的…

大数据毕业设计之前端02:架构布局和aside的设计

前言 上一篇主要讲了我学习前端的一个经历&#xff0c;以及为什么选择BuildAdmin作为深入前端学习的原因.同事也大致聊了一下学习前端需要使用哪些技术栈。 本篇文章来拆解一下BuildAdmin的前端代码结构&#xff0c;和布局实现的细节。 前端代码结构 必须先了解项目的结构&…

2.vue学习笔记(目录结构+模板语法+属性绑定)

1.目录结构 1.vscode ——VSCode工具的配置文件夹 2.node_modules ——Vue项目的运行依赖文件夹 3.public ——资源文件夹&#xff08;浏览器图标&#xff09; 4.src ——源码文件夹 5..gitgnore ——git忽略文件 6.index.html ——如果html文件 7.package.json —…

两数之和(map结构解决)

/*** param {number[]} nums* param {number} target* return {number[]}*/ var twoSum function(nums, target) {let mapIds new Map()for (const [index, item] of nums.entries()) {// 利用map结构不重复的特点&#xff0c;用目标值减去当前值&#xff0c;也就是 9-27// 然…

如何进行代码混淆?方法与常见工具介绍

​ 如何进行代码混淆&#xff1f;方法与常见工具介绍 目录 什么是代码混淆&#xff1f; 代码混淆的方法 常见代码混淆工具 什么是代码混淆&#xff1f; 代码混淆是指将计算机程序的代码转换成一种功能上等价&#xff0c;但难于阅读和理解的形式的行为。混淆后的代码很难被…

P2 Qt Creator创建第一个Qt程序

前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《LLinux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ &#x1f33a;本篇简介 &#xff1a;这一章我们学…

远程调试Linux服务器上的代码

远程调试Linux服务器上的代码 首先我们的环境有本地环境还有研发环境&#xff0c;本地环境就是我们本地电脑上面的代码&#xff0c;而研发环境就是我们开发好一个功能&#xff0c;发到一个linux服务器上面的代码&#xff1b;但是有些时候&#xff0c;研发环境上面的代码可能会…

实时视频美颜:美颜SDK选择指南与性能比较

如今&#xff0c;实时视频美颜技术逐渐成为开发者和用户关注的焦点。本文将通过探讨美颜SDK的选择指南以及性能比较&#xff0c;开发者们可以作为参考&#xff0c;以此选择更合适的美颜SDK。 一、美颜SDK的选择指南 1.1目标应用场景 在选择美颜SDK之前&#xff0c;首先需要…

打工人副业变现秘籍,某多/某手变现底层引擎-StableDiffusion模型下载使用

一、模型的概念 首先要了解 Stable Diffusion 中的模型概念是什么?维基百科对模型的定义非常简单:用一个较为简单的东西来代表另一个东西。换句话说,模型代表的是对某一种事物的抽象表达。 在 AIGC 领域,为了使机器表现出智能,研发人员使用机器学习的方式让计算机从数据中…

Cloak斗篷技术不知道?超实用干货,FP商家必读!

都2023年了&#xff0c;还有很多人犹豫要不要入局独立站。心动不如行动&#xff0c;如果想要在跨境独立站中干出一番新事业&#xff0c;要赶紧动起来。部分卖家在出海初期&#xff0c;都想着出售FP产品&#xff0c;涉及到FP产品&#xff0c;各大主流平台上都有严格的审核机制&a…

生产上线需要注意的安全漏洞

一、关闭swagger 1、关闭swagger v3 # 需同时设置auto-startupfalse&#xff0c;否则/v3/api-docs等接口仍能继续访问 springfox:documentation:enabled: falseauto-startup: falseswagger-ui:enabled: false 2、关闭swagger v2 # 只要不是true就不启用 swagger:enable: fa…

【面试经典150 | 二叉树】相同的树

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;递归方法二&#xff1a;迭代 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题…

python深浅拷贝

【 一 】Python 深拷贝和浅拷贝概念理解 个人见解&#xff1a; 浅拷贝&#xff0c;指的是重新分配一块内存&#xff0c;创建一个新的对象&#xff0c;但里面的元素是原对象中各个子对象的引用。 深拷贝&#xff0c;是指重新分配一块内存&#xff0c;创建一个新的对象&…