总结线程池

目录

导言:

正文:

1.概念

2.线程池的组成和基本原理

3.使用ThreadPoolExecutor创建线程池

4.使用Executors 创建常见的线程池

总结:


导言:

虽然创建销毁线程比创建销毁进程更轻量, 但是在频繁创建销毁线程的时候还是会比较低效。线程池就是为了解决这个问题。 如果某个线程不再使用了,并不是真正把线程释放, 而是放到一个 "池子" 中, 下次如果需要用到线程就直接从池子中取, 不必通过系统来创建了。

正文:

1.概念

线程池是一种用于管理和复用线程的技术,它可以帮助我们更有效地管理线程的生命周期,提高程序的性能和资源利用率。在Java中,线程池通常通过是java.util.concurrent.ExecutorService接口及其实现类来完成的。

2.线程池的组成和基本原理

Java线程池主要由以下几个部分组成:

  • 任务队列(Task Queue)

用于存放待执行的任务,例如无界队列LinkedBlockingQueue、有界队列ArrayBlockingQueue等。

  • 工作线程(Worker Threads)

工作线程是线程池中实际执行任务的线程。

线程池中的线程,用于执行任务队列中的任务。

  • 线程池管理器(ThreadPool Manager)

用于管理线程池的生命周期,负责线程的创建、销毁和管理。

  • 拒绝策略(Rejected Policy)

拒绝策略定义了当线程池无法接收新任务时的处理方式。当任务队列已满且线程池中的线程数已达到最大线程数时,拒绝策略会决定如何处理无法执行的任务,可以是抛出异常、丢弃任务或执行其他处理方式。

工作原理:

当一个任务提交给线程池时,线程池会根据当前的工作线程数和任务队列的状态来决定如何执行这个任务。如果工作线程数小于核心线程数,线程池会创建新的线程来执行任务;如果工作线程数等于核心线程数,任务会被放入任务队列等待执行;如果任务队列满了且工作线程数小于最大线程数,线程池会创建新的线程来执行任务;如果任务队列满了且工作线程数等于最大线程数,新提交的任务会根据拒绝策略进行处理。

3.使用ThreadPoolExecutor创建线程池

ThreadPoolExecutor是Java中用于自定义线程池的类,它实现了ExecutorService接口,提供了更灵活的线程池配置和管理,但是这个类的参数有点复杂,和线程池的组成息息相关。

ThreadPoolExecutor的构造函数定义如下:

public ThreadPoolExecutor(    int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

参数说明:

  • corePoolSize:线程池的基本大小,即就算线程是空闲的,线程池也会保留在池中的线程数。
  • maximumPoolSize:线程池允许创建的最大线程数。
  • keepAliveTime:当线程数大于核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间,超过这个时间的空闲线程会被回收。
  • unit:时间单位,用于指定keepAliveTime的单位。
  • workQueue:用于在执行任务之前保存任务的队列。这个队列将仅保存Runnable任务。
  • threadFactory:线程工厂,用于创建新线程。如果没有提供,默认使用Executors.defaultThreadFactory()。
  • handler:拒绝策略,由于达到线程界限和队列容量而不能执行任务时所使用的处理程序。如果没有提供,默认使用ThreadPoolExecutor.AbortPolicy()。

内置的拒绝策略主要包含以下几种:

  1. AbortPolicy:这是默认的拒绝策略。如果任务无法提交,将会抛出RejectedExecutionException异常。这种策略对于提交任务的线程是一个阻塞,因为它需要等待捕获并处理这个异常。

  2. CallerRunsPolicy:在这种策略下,如果任务无法提交给线程池,那么将会在提交任务的线程(调用者线程)中执行这个任务。这种方式可以保证任务最终会被执行,但是可能会影响性能,因为任务是在调用者线程而不是在线程池线程中执行的。

  3. DiscardPolicy:在这种策略下,如果任务无法提交给线程池,那么这个任务将被直接丢弃,不会产生任何异常。这种策略可能会导致任务的丢失,但是可以避免因任务过多而导致系统崩溃。

  4. DiscardOldestPolicy:在这种策略下,如果任务无法提交给线程池,那么将会丢弃工作队列中等待时间最长的任务(即队列头部的任务),然后将新任务加入到工作队列的尾部。这种方式可以确保新任务有机会被执行,但是可能会导致一些较旧的任务被丢弃。

 ThreadPoolExecutor的基本方法:

  • execute(Runnable command):执行一个任务。
  • submit(Runnable task):提交一个任务,并返回一个Future对象,可以通过这个对象来获取任务执行的结果或者取消任务。
  • submit(Runnable task, T result):提交一个任务,并给定一个结果,当任务成功完成时,将结果与任务的输出关联起来。
  • shutdown():开始关闭线程池,不再接受新任务,但会完成已提交的任务。
  • awaitTermination(long timeout, TimeUnit unit):等待所有任务完成,或者超过指定的等待时间。
  • isShutdown():检查线程池是否已关闭。
  • isTerminated():检查线程池是否已终止,即所有任务都已完成。
  • getActiveCount():获取当前活动的线程数(即正在执行任务的线程数)。

示例代码:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorExample {

    public static void main(String[] args) {
        // 创建一个线程池,核心线程数为2,最大线程数为4,最大空闲时间为30s,使用有界队列,超出核心线程数的任务会被放入队列中
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, 4, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));

        // 提交5个任务给线程池执行
        for (int i = 1; i <= 5; i++) {
            //变量捕获
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

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

在这个示例中,我们创建了一个ThreadPoolExecutor实例,设置核心线程数为2,最大线程数为4,使用有界队列(ArrayBlockingQueue)来存储等待执行的任务。然后我们提交了5个任务给线程池执行,每个任务打印自己的任务ID并休眠1秒模拟任务执行。

结果如下:

4.使用Executors 创建常见的线程池

ThreadPoolExecutor的创建方式虽然比较自由但是过于繁琐,java中也可以使用Executors更简单的创建线程池。

1. 创建固定大小的线程池

Executors.newFixedThreadPool(int nThreads)方法创建了一个固定大小的线程池。这个线程池中的线程数量始终保持不变,如果某个线程因异常结束,会创建一个新线程来替代它。

int numberOfThreads = 5; // 指定线程池的大小
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(numberOfThreads);

// 提交任务到线程池
for (int i = 0; i < 10; i++) {
    fixedThreadPool.submit(() -> {
        System.out.println("Task " + i + " is running on thread " + Thread.currentThread().getName());
    });
}

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

2. 创建单线程的执行器

Executors.newSingleThreadExecutor()方法创建了一个单线程的执行器。这个执行器确保所有任务按照指定顺序执行,因为它只有一个线程来执行任务。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

// 提交任务到执行器
singleThreadExecutor.submit(() -> {
    System.out.println("Running the only task in the single-threaded executor");
});

// 关闭执行器
singleThreadExecutor.shutdown();

 3. 创建可缓存的线程池
Executors.newCachedThreadPool()方法创建了一个可缓存的线程池。如果当前线程数超过了处理需求,将回收空闲的线程。当需求增加时,会创建新的线程,直到达到Integer.MAX_VALUE。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// 提交任务到线程池
cachedThreadPool.submit(() -> {
    System.out.println("Running a task in the cached thread pool");
});

// 通常不需要手动关闭可缓存的线程池,因为它会根据需要自动管理线程

4.创建定时任务的线程池
Executors.newScheduledThreadPool(int corePoolSize)方法创建了一个用于执行定时任务的线程池。这个线程池会根据任务的调度时间来执行任务。

int corePoolSize = 5; // 指定线程池的核心线程数
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(corePoolSize);

// 安排任务在未来的某个时间点执行
scheduledThreadPool.schedule(() -> {
    System.out.println("Running a scheduled task");
}, 5, TimeUnit.SECONDS);

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

可以看到使用Executors可以比较简单的创建线程池,但需要注意的是newFixedThreadPool(int nThreads)和newSingleThreadExecutor()创建的线程池在不再需要时应该调用shutdown()方法来关闭,以释放资源。而其它的方法不需要自己关闭,它们会自己调整。

总结:

线程池是多线程编程中常用的工具,能够有效管理线程并提高程序性能。合理使用线程池可以避免线程创建和销毁的开销,提高系统的稳定性和效率。线程池注意有两种创建方式,具体选择哪一种可以根据自己的需求来判断。

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

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

相关文章

深度学习transformer架构详细详解

一、transformer的贡献 transformer架构的贡献&#xff1a;该架构只使用自注意力机制&#xff0c;没有使用RNN或卷积网络。且可以实现并行计算&#xff0c;加快模型训练速度。 &#xff08;将所有的循环层全部换成&#xff1a;multi-headed self-attention&#xff09; 二、t…

JavaScript运算符(赋值、自增自减、比较、逻辑、展开、优先级)、分支语句(if、三元表达式、switch)、循环结构(while、for)、断点调试

目录 1. 运算符1.1 赋值运算符1.2 自增和自减运算符1.3 比较运算符1.4 逻辑运算符1.5 展开运算符1.6 运算符优先级 2. 分支语句2.1 if2.2 三元表达式2.3 switch 3. 循环结构3.1 while循环3.2 for循环 4. 断点调试 1. 运算符 1.1 赋值运算符 -*/% 1.2 自增和自减运算符 前置…

(C++) 树状数组

目录 一、介绍 二、一维树状数组 2.1 区间长度 2.2 前驱和后继 2.3 查询前缀和 2.4 点更新 三、一维数组的实现 3.1 区间长度函数 3.2 前缀和 3.3 插入/更新 3.4 封装成类 一、介绍 树状数组&#xff08;Binary Indexed Tree&#xff0c;BIT&#xff09;&#xff0c;又称为 …

ActiveMQ 如果数据处理出现异常会怎么样

我们有一个 Spring 的客户端&#xff0c;在处理消息的时候因为程序的原因出现消息处理异常。 对这种情况&#xff0c;ActiveMQ 会把出现异常的消息放在 DLQ 队列中进行持久化。 因此&#xff0c;在 ActiveMQ 消息处理队列中需要持续关注 DLQ 队列&#xff0c; DLQ 的队列都是无…

线段树汇总

线段树是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数&#xff0c;时间复杂度为O(logN)。而未优化的空间复杂度为2N&a…

最新版的GPT-4.5-Turbo有多强

OpenAI再次用实力证明了&#xff0c;GPT依然是AI世界最强的玩家&#xff01;在最新的AI基准测试中&#xff0c;OpenAI几天前刚刚发布的GPT-4-Turbo-2024-04-09版本&#xff0c;大幅超越了Claude3 Opus&#xff0c;重新夺回了全球第一的AI王座&#xff1a; 值得一提的是&#xf…

【机器学习】重塑汽车设计与制造:实例与代码探索

机器学习重塑汽车设计与制造 一、机器学习在汽车设计中的应用二、机器学习在智能制造与生产中的应用 在数字化浪潮的推动下&#xff0c;机器学习技术正逐步成为汽车行业的创新引擎。从概念设计到智能制造&#xff0c;机器学习正以其独特的优势助力汽车产业的革新与发展。本文将…

实现基于RAG的QA应用程序

实现基于RAG的Q&A应用程序 LLM 支持的最强大的应用程序之一是复杂的 问答 &#xff08;Q&A&#xff09; 聊天机器人。这些应用程序可以 回答有关特定来源信息的问题。这些应用程序 使用一种称为检索增强生成 &#xff08;RAG&#xff09; 的技术。 什么是检索增强生成…

Golang | Leetcode Golang题解之第43题字符串相乘

题目&#xff1a; 题解&#xff1a; func multiply(num1 string, num2 string) string {if num1 "0" || num2 "0" {return "0"}m, n : len(num1), len(num2)ansArr : make([]int, m n)for i : m - 1; i > 0; i-- {x : int(num1[i]) - 0fo…

设计模式之访问者模式(上)

访问者模式 1&#xff09;概述 1.概念 访问者模式包含访问者和被访问元素两个主要组成部分。 处方单中的各种药品信息就是被访问的元素&#xff0c;而划价人员和药房工作人员就是访问者&#xff0c;被访问的元素通常具有不同的类型&#xff0c;且不同的访问者可以对它们进行…

上位机图像处理和嵌入式模块部署(树莓派4b处理类muduo网络编程)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 既然是linux编程&#xff0c;那么自然少不了网络编程。在linux平台上面&#xff0c;有很多的网络编程库可以选择&#xff0c;大的有boost、qt&…

免费PNG素材网站推荐:设计效率倍增!

一、即时设计 新一代协同设计工具即时设计&#xff0c;内置丰富社区资源&#xff0c;可以在此获得设计前线的各类PNG图像&#xff0c;以及矢量图标&#xff0c;包括毛玻璃、3D混搭、全息投影、单色、平面化等&#xff0c;都是符合目前市场的主流风格。通过最近更新、作品、资源…

影响钕铁硼磁钢性能的因素及方法

钕铁硼永磁材料自问世以来&#xff0c;就以其优越的磁性能而备受关注&#xff0c;被称为“磁王“&#xff0c;在市场需求的不断地增长下&#xff0c;钕铁硼生产工艺及磁体性能也不断发展和提升。我们一般用剩磁、矫顽力和最大磁能积这几个指标来衡量磁性材料的磁性能。 剩磁 B…

【C++】:类和对象(上)

目录 一&#xff0c;面向过程和面向对象初步认识二&#xff0c;类的引入三&#xff0c;类的定义3.1 **类的说明**3.2 **类的访问限定符**3.3 **类的两种实现方式**3.4 **成员变量的命名规则 --- 加下划线** 四&#xff0c;类的作用域4.1 **类域的说明**4.2 **类域与命名空间域的…

分析经过j2k压缩的dicom文件经验分享

最近碰到一个问题&#xff0c;在网上搜到是用JPEG 2000压缩的DICOM文件 JPEG 2000对应的transfer syntax UID为 1.2.840.10008.1.2.4.91 参考:https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_8.7.3.html 该文件是用专业德国老牌开发库DCMTK生成的 (…

虚拟机VMware安装与Ubuntu

1.虚拟机安装 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;2fr6 CG54H-D8D0H-H8DHY-C6X7X-N2KG6 2.Ubuntu下载 Download Ubuntu Desktop | Ubuntu 3.设置 如后续要下一些软件越大越好

Diffusion Model原理剖析

目录 前言1. DDPM演算法初览2. 图像生成模型共同目标3. VAE: Lower bound of l o g P ( x ) logP(x) logP(x)4. Diffusion Model背后的数学原理5. 为什么需要Sample?6. Diffusion Model的应用7. Diffusion Model成功的关键总结参考 前言 接着上篇文章 图像生成模型浅析&#…

15.C++常用的算法_拷贝和替换算法

文章目录 遍历算法1. copy()代码工程运行结果 2. replace()代码工程运行结果 3. replace_if()代码工程运行结果 4. swap()代码工程运行结果 遍历算法 1. copy() 代码工程 copy()函数不要因为使用而使用#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include&l…

dremio支持设置

Dremio 支持提供可用于诊断目的的设置。这些设置通过 Dremio UI&#xff1a;设置>支持启用&#xff08;或禁用&#xff09; 使用 Client Tools 可以配置当用户查看数据集中的数据时&#xff0c;Dremio 项目的工具栏上显示哪些客户端应用程序按钮。用户可以通过单击相应的工具…

免费ssl泛域名/泛解析证书获取教程

泛域名SSL证书&#xff0c;也称为通配符证书&#xff0c;它可以保护一个主域名下的所有子域名。这意味着&#xff0c;无论你有多少个子域名&#xff0c;只要安装了一个泛域名SSL证书&#xff0c;就可以实现全部子域名的安全保护。这种证书非常适合大型企业或有大量子域名的网站…