Java线程池的使用和最佳实践

第1章:引言

处理并发问题时,如果每次都新建线程,那系统的压力得有多大?这时候,线程池就像一个英雄一样出现了,它帮我们有效地管理线程,提高资源利用率,降低开销。那么,为什么说线程池这么重要呢?首先,线程池能够控制系统中执行线程的数量,这样就减少了线程创建和销毁的开销,提高了系统的响应速度。其次,通过合理的配置,线程池能够提供更好的系统稳定性,避免因为线程数量过多而导致系统崩溃。

小黑今天就带咱们一起深入了解Java中的线程池,看看它是怎么回事,怎么用,以及如何在我们的代码里发挥最大的效力。

第2章:线程池的基本原理

要想彻底搞懂线程池,咱们得先弄明白它的基本原理。线程池,顾名思义,就是存放线程的池子。但它不仅仅是简单的存放,更重要的是它对线程进行了有效的管理。在Java中,线程池通过Executor接口和其实现类ThreadPoolExecutor来提供。

说到底,线程池的核心思想就是复用已有线程。当任务来临时,线程池会尝试使用已存在的线程,而不是每次都新建。如果所有线程都在忙,线程池会根据配置决定是创建新线程,还是放到一个队列中等待。这就大大减少了线程创建和销毁的开销,提高了响应速度。

现在,咱们来看一段示例代码,理解线程池的创建和使用:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {Runnable worker = new WorkerThread("" + i);executor.execute(worker);}executor.shutdown();while (!executor.isTerminated()) {}System.out.println("所有任务已完成");}
}class WorkerThread implements Runnable {private String command;WorkerThread(String s) {this.command = s;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 开始. 命令 = " + command);processCommand();System.out.println(Thread.currentThread().getName() + " 结束.");}private void processCommand() {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic String toString() {return this.command;}
}

在这个例子中,咱们创建了一个固定大小的线程池,并提交了10个任务。这就是线程池的魅力所在:管理和复用线程,让咱们的程序更加高效。

PS: 小黑收集整理了一份超级全面的复习面试资料包,在这偷偷分享给你~
链接:https://sourl.cn/gUV3UP 提取码:fjb3

第3章:Java中的线程池类型

Java提供了几种不同类型的线程池,每种都有它的特点和用途。这一章节,小黑要带大家了解这些类型,看看它们各自适合什么场景。

1. 固定数量线程池(FixedThreadPool)

先说说FixedThreadPool。顾名思义,这种线程池的线程数量是固定的。它适合于负载相对平稳的场景,线程数量不变意味着不会频繁地创建和销毁线程,效率比较高。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
// 使用这个线程池来执行任务
2. 可缓存线程池(CachedThreadPool)

然后是CachedThreadPool,这个线程池可以根据需要创建新线程,但如果之前创建的线程可用,就会重用它们。它非常适合于任务数量动态变化的场景。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 适用于任务数动态变化的情况
3. 单线程化线程池(SingleThreadExecutor)

SingleThreadExecutor,这个线程池里只有一个线程在工作。它保证了所有任务都在同一个线程按顺序执行,这对于需要保证执行顺序的场景非常有用。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 用于需要顺序执行任务的场景
4. 定时及周期性任务线程池(ScheduledThreadPool)

最后是ScheduledThreadPool,这个线程池特别适合需要执行定时或周期性任务的场景。你可以设定任务在指定的延迟后执行,或者定期执行。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 使用这个线程池来执行定时或周期性任务

咱们看到,Java给我们提供了不同类型的线程池,每一种都有其独特的使用场景。选择正确的线程池类型,可以大大提高程序的性能和稳定性。不过记住,不同类型的线程池适用于不同的应用场景,咱们在选择时要根据实际情况来定。这样,咱们就能把线程池的潜力发挥到极致!

第4章:创建自定义线程池

接下来小黑要和大家聊聊如何创建自定义线程池。虽然Java提供了几种现成的线程池,但有时候咱们需要根据具体情况来定制自己的线程池。这就需要用到ThreadPoolExecutor类。

ThreadPoolExecutor类提供了丰富的构造器,让我们可以精细地控制线程池的行为,比如线程数量、存活时间、工作队列等。现在,小黑就来给大家展示一下如何使用这个类来创建一个符合自己需求的线程池。

首先,咱们来看看ThreadPoolExecutor构造器的参数:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
  • corePoolSize:核心线程数,即即使线程是空闲的,线程池也会保持存活的线程数。
  • maximumPoolSize:线程池允许的最大线程数。
  • keepAliveTime:当线程数超过核心线程数时,多余的空闲线程的存活时间。
  • unitkeepAliveTime的时间单位。
  • workQueue:工作队列,用于存放待执行的任务。
  • threadFactory:线程工厂,用于创建线程。
  • handler:拒绝策略,当线程池和工作队列都满了,如何处理新加入的任务。

下面是一个创建自定义线程池的示例:

import java.util.concurrent.*;public class CustomThreadPoolExample {public static void main(String[] args) {int corePoolSize = 5;int maxPoolSize = 10;long keepAliveTime = 5000;ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());// 提交任务到线程池for (int i = 0; i < 20; i++) {executor.execute(new Task("" + i));}executor.shutdown();}
}class Task implements Runnable {private String name;public Task(String name) {this.name = name;}public void run() {System.out.println("Executing : " + name);}
}

在这个例子中,小黑创建了一个核心线程数为5,最大线程数为10的线程池。线程空闲时间设置为5000毫秒,使用了LinkedBlockingQueue作为工作队列,当线程池和队列都满了的时候,使用CallerRunsPolicy策略。

通过这种方式,咱们就可以根据实际需求创建一个最适合自己的线程池了。记住,合理配置线程池的参数对于提高程序的性能和稳定性至关重要。

第5章:线程池的关键配置及其最佳实践

走到这一步,咱们已经知道了如何创建线程池,那接下来小黑要和大家聊聊线程池的关键配置和一些最佳实践。这些配置和实践可以帮助咱们更好地使用线程池,提高程序的性能和稳定性。

核心线程数(corePoolSize)

核心线程数是线程池中始终保持活跃的线程数量,即使它们没有任务在执行。设置这个值时,咱们要考虑到系统资源的限制和任务的实际需求。如果设置得太高,可能会浪费系统资源;设置得太低,又可能导致处理能力不足。

最大线程数(maximumPoolSize)

最大线程数定义了线程池可以创建的最大线程数量。当工作队列满了之后,线程池会开始创建新线程,直到达到这个数值。合理的设置这个值对于防止系统过载非常重要。

空闲线程的存活时间(keepAliveTime)

当线程池中线程数量超过核心线程数时,多余的线程会在空闲一定时间后被终止,这个时间就是空闲线程的存活时间。这个配置可以帮助系统在不忙碌的时候释放资源。

工作队列(workQueue)

工作队列用于存放等待执行的任务。队列的类型对于线程池的行为有很大影响。例如,LinkedBlockingQueue通常用于固定大小的线程池,而SynchronousQueue适用于缓存线程池。

拒绝策略(RejectedExecutionHandler)

当线程池和工作队列都满了,我们必须定义一个拒绝策略来处理新加入的任务。Java提供了几种标准的拒绝策略,如AbortPolicy(抛出异常)、CallerRunsPolicy(在调用者的线程中执行任务)等。

最佳实践
  1. 正确估算线程需求:根据任务的性质(CPU密集型、IO密集型)和系统环境来合理设置核心线程数和最大线程数。
  2. 合理选择工作队列:根据任务的数量和类型选择适合的队列类型。
  3. 合理配置拒绝策略:根据业务需求选择合适的拒绝策略。
  4. 监控线程池状态:定期监控线程池的状态,包括线程数量、活跃度、任务数量等,以便及时调整配置。

通过以上这些配置和实践,咱们可以更有效地管理线程池,确保应用程序的高效稳定运行。记住,没有一成不变的规则,关键在于根据实际情况灵活调整。

第6章:线程池的常见问题及解决策略

本章和大家探讨一下线程池可能遇到的一些常见问题以及解决这些问题的策略。理解这些问题及其解决方法对于确保线程池稳定高效地运行至关重要。

线程池的常见问题
  1. 线程饥饿死锁:当线程池中的线程都在等待其他任务完成,而这些任务也需要线程池中的线程来执行时,就会发生线程饥饿死锁。

  2. 资源耗尽:如果线程池的最大线程数设置得过高,可能会耗尽系统资源,导致性能下降,甚至崩溃。

  3. 任务拒绝:当线程池满了且工作队列也满时,新提交的任务会被拒绝。

  4. 线程泄露:在某些情况下,线程可能因为未能正确处理异常而永远卡在某个状态,导致线程泄露。

解决策略
  1. 避免线程饥饿死锁:合理配置核心线程数和最大线程数,确保有足够的线程来处理任务。另一种策略是使用不同的线程池来处理不同类型的任务。

  2. 资源管理:合理设置最大线程数和工作队列的大小,以避免资源耗尽。监控系统的性能指标,如CPU和内存使用率,可以帮助及时调整线程池配置。

  3. 合理的拒绝策略:选择合适的拒绝策略,如CallerRunsPolicy,可以让提交任务的线程自己执行该任务,从而降低对线程池的压力。

  4. 异常处理:确保任务执行过程中的异常被妥善处理,避免线程因异常而无法继续执行其他任务。

让我们来看一个简单的示例,展示如何处理任务执行中的异常:

public class SafeTask implements Runnable {@Overridepublic void run() {try {// 执行任务的逻辑} catch (Exception e) {// 处理异常}}
}// 使用线程池执行任务
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new SafeTask());

在这个例子中,SafeTask类中的run方法内部处理了可能发生的异常,这样即使在执行任务时出现异常,线程也不会因此退出,而是可以继续执行其他任务。

理解并解决这些常见问题,将有助于我们更好地利用线程池,保持应用程序的稳定和高效。

第7章:实际案例分析

接下来小黑要和大家分享一些实际的线程池使用案例。通过这些案例,咱们可以更好地理解线程池在实际项目中是如何发挥作用的。

案例一:Web服务器处理请求

想象一下,咱们有一个Web服务器,它需要处理成百上千的并发请求。如果为每个请求创建一个新线程,系统很快就会因为线程过多而崩溃。这时,线程池就派上用场了。

// 创建一个固定大小的线程池
ExecutorService pool = Executors.newFixedThreadPool(100);// 模拟处理请求
for (int i = 0; i < 1000; i++) {pool.execute(new HttpHandler());
}// HttpHandler类处理实际的请求
class HttpHandler implements Runnable {public void run() {// 处理HTTP请求的逻辑}
}

在这个案例中,线程池限制了同时处理的请求数量,保证了系统的稳定性。

案例二:数据处理和分析

假设小黑现在有一个任务是处理大量数据并进行分析。这些数据处理任务是独立的,可以并行执行以提高效率。

// 创建一个可缓存的线程池
ExecutorService pool = Executors.newCachedThreadPool();// 模拟数据处理任务
for (Data data : dataList) {pool.execute(new DataProcessor(data));
}// DataProcessor类处理数据
class DataProcessor implements Runnable {private Data data;DataProcessor(Data data) {this.data = data;}public void run() {// 数据处理逻辑}
}

在这个案例中,可缓存的线程池可以根据需要创建新线程,从而提高了数据处理的效率。

通过这些案例,咱们可以看到,线程池在不同场景下如何有效地提高系统性能,同时保证稳定性和可靠性。记住,理论知识很重要,但将知识应用到实际问题中才能真正理解和掌握它。

第8章:总结

线程池是Java并发编程中非常强大的工具,它能有效地管理线程,提高资源利用率,增强程序的响应速度。但同时,合理配置和使用线程池也非常关键,这关系到程序的性能和稳定性。咱们在使用线程池时,要考虑到核心线程数、最大线程数、工作队列、线程存活时间以及拒绝策略等多个方面。


面对寒冬,我们更需团结!小黑收集整理了一份超级强大的复习面试资料包,也强烈建议你加入我们的Java后端报团取暖群,一起复习,共享各种学习资源,互助成长。无论是新手还是老手,这里都有你的位置。在这里,我们共同应对职场挑战,分享经验,提升技能,闲聊副业,共同抵御不确定性,携手走向更稳定的职业未来。让我们在Java的路上,不再孤单!进群方式以及资料,点击如下链接即可获取!

链接:https://sourl.cn/gUV3UP 提取码:fjb3

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

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

相关文章

代理模式介绍(静态代理、jdk动态代理、cglib代理)

一、静态代理 &#xff08;一&#xff09;定义 1、定义 为其他对象提供一种代理以控制对这个对象的访问&#xff1b; 2、涉及到的角色 &#xff08;1&#xff09;抽象主题角色&#xff1a;真实主题和代理主题的共同接口&#xff0c;便于在使用真实主题的地方都可以使用代理…

鸿蒙开发笔记

最近比较火&#xff0c;本身也是做前端的&#xff0c;就抽空学习了下。对前端很友好 原视频地址&#xff1a;黑马b站鸿蒙OS视频 下载安装跟着视频或者文档就可以了。如果你电脑上安装的有node&#xff0c;但是开发工具显示你没安装&#xff0c;不用动咱们的node&#xff0c;直…

红队攻防实战之Access注入

若盛世将倾&#xff0c;深渊在侧&#xff0c;我辈当万死以赴 访问漏洞url: 1.Access联合查询 判断是否有注入 and 11正常&#xff0c;and 12出错 判断字段数 order by 7正常 order by 8出错 爆破出表名并判断回显点为2&#xff0c;5 查看字段内容&#xff0c;将字段名填入回…

12月1号作业

实现运算符重载 #include <iostream>using namespace std; class Person{friend const Person operator-(const Person &L,const Person &R);friend bool operator<(const Person &L,const Person &R);friend Person operator-(Person &L,const …

WakaTime一个用于跟踪和分析编程时间的工具

WakaTime是一个用于跟踪和分析编程时间的工具&#xff0c;它可以集成到各种代码编辑器和集成开发环境中&#xff0c;例如Visual Studio Code、Sublime Text、PyCharm等。它可以帮助开发人员了解他们花费在不同项目和编程语言上的时间&#xff0c;以及他们的编码习惯和生产力。 …

【面试HOT200】二叉树——广度优先搜索篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot200】进行的&#xff0c;每个知识点的修正和深入主要参…

SpringSecurity工作原理

实现功能就是继承这几个对应功能的类。 大概工作流程 Spring Security 的过滤器&#xff08;Filters&#xff09;和拦截器&#xff08;Interceptors&#xff09;是 Spring Security 框架中用于保护 web 应用安全的重要组件。它们在处理 HTTP 请求时扮演不同的角色&#xff0c…

uni-app一些目录结构、方法、生命周期、打包、微信小程序登录与支付

1、关于uniapp的目录结构 跟普通vue项目目录结构差不多&#xff0c;多了几个核心文件&#xff0c;manifest.json是配置应用名称、appid、logo、版本等打包信息用的&#xff0c;pages.json的作用是配置页面路径、页面窗口样式、tabBar、navigationBar等页面类信息 2、页面适配方…

【Node.js】笔记梳理 8 - API和JWT

写在最前&#xff1a;跟着视频学习只是为了在新手期快速入门。想要学习全面、进阶的知识&#xff0c;需要格外注重实战和官方技术文档&#xff0c;文档建议作为手册使用 系列文章 【Node.js】笔记整理 1 - 基础知识【Node.js】笔记整理 2 - 常用模块【Node.js】笔记整理 3 - n…

12月03日,每日信息差/菲律宾发生7.4级强震后共录得955次余震/腾讯惩处超 400 万个 QQ 号:这三大行为零容忍

_灵感 ​ &#x1f396; 中国联通&#xff1a;选举陈忠岳为公司董事长 &#x1f384; 菲律宾发生7.4级强震后共录得955次余震 &#x1f30d; 京沪高铁二线“收官段”尘埃落定&#xff1a;潍宿高铁初步设计正式获批 &#x1f30b; 我国燃料电池汽车产业进入提速关键期 &#…

树与二叉树堆:经典OJ题集(2)

目录 二叉树的性质及其问题&#xff1a; 二叉树的性质 问题&#xff1a; 一、对称的二叉树&#xff1a; 题目&#xff1a; 解题思路&#xff1a; 二、另一棵树&#xff1a; 题目&#xff1a; 解题思路&#xff1a; 三、翻转二叉树&#xff1a; 题目&#xff1a;…

synchronized和volatile的区别是什么?

synchronized和volatile是Java中的两个关键词&#xff0c;分别用于实现线程同步和线程间的可见性。 synchronized用于实现线程之间的互斥同步&#xff0c;即同一时刻只能有一个线程访问被synchronized修饰的代码块或方法&#xff0c;其他线程需要等待。synchronized确保了线程…

非功能关键知识总结(一)

文章目录 一、稳定性(一)、服务级别协议1、SLA2、OLA3、UC (二)、可用性指标(三)、突发事件等级 三、质量(一)、千行代码缺陷数量(二)、软件质量模型的发展(三)、产品质量模型 四、安全(一)、网络安全 五、灾备(一)、灾备指标(二)、灾难恢复等级(三)、容灾技术分类 一、稳定性 …

一次电气——电抗器(一)

我之前的工作是在国外建联合循环电厂&#xff0c;现在的工作是研发一次电力设备。虽然仍是在电力行业发展&#xff0c;但这两份不同岗位不同职能的工作究其感受而言有很大的不同。相较于第一份工作&#xff0c;第二份工作带给我带来的更多的是一种由广及微&#xff0c;由浅入深…

kafka3.6.0部署

部署zk https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.9.1/apache-zookeeper-3.9.1.tar.gz tar -xf apache-zookeeper-3.9.1.tar.gz -C /apps cd /apps/ && ln -s apache-zookeeper-3.9.1 zookeeper 修改配置bash grep -vE ^$|^# conf/zo…

buuctf [极客大挑战 2019]Havefun1

解题思路&#xff1a; 小习惯 本题先看看源码或者检查一下&#xff0c;可能这是俺的一个小习惯。 源码里面都看到了php的代码 php代码解析&#xff1a; $cat$_GET[cat]; echo $cat; if($catdog){ echo Syc{cat_cat_cat_cat}; } 1.$ca…

<蓝桥杯软件赛>零基础备赛20周--第8周第2讲--排序的应用

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…

模板可变参数/包装器

一、什么是模板可变参数 1、对比函数可变参数 可变参数即参数的数量是不确定的&#xff0c;底层根据用户传入的数量&#xff0c;开一个数组存储对应的参数。 2、基本形式 args -- argument 参数 [0,n]个参数 // Args是一个模板参数包&#xff0c;args是一个函数形参参数包…

课题学习(十四)----三轴加速度计+三轴陀螺仪传感器-ICM20602

本篇博客对ICM20602芯片进行学习&#xff0c;目的是后续设计一个电路板&#xff0c;采集ICM20602的数据&#xff0c;通过这些数据对各种姿态解算的方法进行仿真学习。 一、 ICM20602介绍 1.1 初识芯片 3轴陀螺仪&#xff1a;可编程全刻度范围(FSR)为250 dps&#xff0c;500 d…