线程池的使用及实现

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

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

  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();}}//submitpublic 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 就可能被拿到并执行了

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

亚信安慧AntDB数据库中级培训ACP上线,中国移动总部首批客户认证通过

近日&#xff0c;亚信安慧AntDB数据库ACP&#xff08;AntDB Certified Professional&#xff09;中级培训课程于官网上线。在中国移动总部客户运维团队、现场项目部伙伴和AntDB数据库成员的协同组织下&#xff0c;首批中级认证学员顺利完成相关课程的培训&#xff0c;并获得Ant…

一键抠图3:Android实现人像抠图 (Portrait Matting)

一键抠图3&#xff1a;Android实现人像抠图 (Portrait Matting) 目录 一键抠图3&#xff1a;Android实现人像抠图 (Portrait Matting) 1. 前言 2. 抠图算法 3. 模型Android部署 &#xff08;1&#xff09; 将Pytorch模型转换ONNX模型 &#xff08;2&#xff09; 将ONNX模…

前端学习微信小程序开发

1.微信小程序项目结构 2.WXML和HTML的区别 3.WXSS与CSS的区别 4.小程序中的.js文件 5.小程序的宿主环境 宿主环境是指程序运行所必须的依赖环境&#xff0c;因此手机微信时小程序的宿主环境。小程序宿主环境包含了通信模型、运行机制、组件、API。 &#xff08;1&#xff09;…

CSS import 规则

导入 “navigation.css” 样式到当前的样式表&#xff1a; import “navigation.css”; /* 使用字符串 / 或者 import url(“navigation.css”); / 使用 url 地址 */ 属性定义及使用说明 CSS import 用于从其他样式表导入样式规则。 import 规则必须在 CSS 文档的头部&#xff…

Ignis - Interactive Fire System

Ignis - 点火、蔓延、熄灭、定制! 全方位火焰系统。 这个插件在21年的项目中使用过很好用值使用概述 想玩火吗?如果想的话,那么Ignis就是你的最佳工具。有了Ignis,你可以把任何物体、植被或带皮带骨的网状物转换为可燃物体,它就会自动着火。然后,火焰可以蔓延,点燃其他物…

初识人工智能,一文读懂过拟合欠拟合和模型压缩的知识文集(3)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

深入了解Git LFS:高效管理大型文件的利器

今天在使用CodeUp上传代码时&#xff0c;我为项目添加了一个大小超过300MB的文件。在进行push操作时&#xff0c;系统提示我“推送失败&#xff0c;以下文件大小超过单文件200MB的系统限额&#xff0c;大文件请使用Git-LFS管理”。于是我开始了解Git LFS。对于需要处理大型二进…

oracle 拼接语句怎么写?

||的妙用&#xff0c;字符串和变量列名之间都得用||分隔&#xff0c;oracle等数据库两个单引号输出一个单引号&#xff0c;因为如果只写了一个的话 他会和最近的单引号被数据库认为是组成了一个空字符串&#xff0c;因此需要用两个单引号来表示这是个单引号 查询列间用&#xf…

力扣541.反转字符串 II

文章目录 力扣541.反转字符串 II示例代码实现总结收获 力扣541.反转字符串 II 示例 代码实现 class Solution {public String reverseStr(String s, int k) {char[] ans s.toCharArray();for(int i0;i<ans.length;i2*k){int begin i;int end Math.min(ans.length-1,begin…

QML中Dialog获取close与open状态

1.新建MyDialog.qml import QtQuick 2.15import QtQuick.Dialogs 1.2Dialog {id: rootvisible: falsetitle: qsTr("弹出对话框")width: 250height: 200} 2.main.qml中调用MyDialog import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15…

整理PC文件续篇 运用百度网盘、TreeSizeFree、HTML轻松管理>100G资料

因为参加了一个视频剪辑班&#xff0c;在几周的时间里光素材和插件就有90G以上&#xff0c;如果加上软件马上就过百G了&#xff0c;如何给这些文件一定的结构&#xff0c;以便以后方便管理和查找&#xff0c;避免淹没在资料堆里是非常迫切的需求。 这些资料都是通过百度网盘分发…

用23种设计模式打造一个cocos creator的游戏框架----(三)外观模式模式

1、模式标准 模式名称&#xff1a;外观模式 模式分类&#xff1a;结构型 模式意图&#xff1a;为一组复杂的子系统提供了一个统一的简单接口。这个统一接口位于所有子系统之上&#xff0c;使用户可以更方便地使用整个系统。 结构图&#xff1a; 适用于&#xff1a; 当你想为…