高效并发处理之必备利器:线程池

📕我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查设计师、个人社区创始人、开源项目贡献者。🌎跑过十五公里、徒步爬过衡山、🔥有过三个月减肥20斤的经历、是个喜欢躺平的狠人。

📘拥有多年一线研发和团队管理经验,研究过主流框架的底层源码(Spring、SpringBoot、Spring MVC、SpringCould、Mybatis、Dubbo、Zookeeper),消息中间件底层架构原理(RabbitMQ、RockerMQ、Kafka)、Redis缓存、MySQL关系型数据库、 ElasticSearch全文搜索、MongoDB非关系型数据库、Apache ShardingSphere分库分表读写分离、设计模式、领域驱动DDD、Kubernetes容器编排等。🎥有从0到1的高并发项目经验,利用弹性伸缩、负载均衡、报警任务、自启动脚本,最高压测过200台机器,有着丰富的项目调优经验。

📙经过多年在CSDN创作上千篇文章的经验积累,我已经拥有了不错的写作技巧。同时,我还与清华大学出版社签下了四本书籍的合约,并将陆续在明年出版。这些书籍包括了基础篇、进阶篇、架构篇的📌《Java项目实战—深入理解大型互联网企业通用技术》📌,以及📚《解密程序员的思维密码–沟通、演讲、思考的实践》📚。具体出版计划会根据实际情况进行调整,希望各位读者朋友能够多多支持!

以梦为马,不负韶华

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

💡在这个美好的时刻,本人不再啰嗦废话,现在毫不拖延地进入文章所要讨论的主题。接下来,我将为大家呈现正文内容。

CSDN

高效并发处理之必备利器:线程池

🌟 Java中的多线程编程

Java中的多线程编程是一种高效的程序设计方式,可以同时处理多个任务,从而提高程序的处理能力。但是线程的创建、调度和销毁都会消耗一定的系统资源,因此,在设计多线程程序时需要使用线程池来管理线程的生命周期。

Java提供了一个java.util.concurrent包,里面提供了一系列的线程池的实现类,开发人员可以通过这些类方便地创建、管理线程池。

在这些类中,最常用的就是Executors类,这个类可以方便地创建几种常见的线程池。

  1. newSingleThreadExecutos

这个方法创建的线程池只有一个线程,也就是说,线程池中只有一个任务在处理,这个方法比较适合需要保证任务顺序执行的场景。

如果需要同时处理多个任务,则不建议使用这个线程池。因为在这个线程池中,一旦当前任务出现异常或者阻塞,整个线程池就会停止工作,后续任务也无法执行。

  1. newFixedThreadPool(int nThreads)

这个方法创建的线程池是固定大小的线程池,线程池中同时运行的线程数量是固定的,即使有空闲线程也不会多开。

这个线程池比较适合处理占用资源较多的任务,比如网络IO操作、文件IO操作等。在这些操作中,线程往往会出现阻塞,因此需要线程池来管理线程的生命周期,并且限制线程的数量。

  1. newCachedThreadPool()

这个方法创建的线程池是无界线程池,也就是说,线程池中可以同时处理任意数量的任务,因为每个任务都会创建一个新的线程来处理。

这个线程池比较适合处理短时间、CPU轻度占用的任务,比如数据计算、图像处理等。如果任务较多,这个线程池可能会导致系统资源耗尽,因此需要合理的设置队列容量或者使用其他种类的线程池。

以下是Java中使用Executors类创建线程池的示例代码:

  1. 创建一个单线程的线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
  1. 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
  1. 创建一个可缓存的线程池
ExecutorService executor = Executors.newCachedThreadPool();

需要注意的是,在实际使用线程池的时候,不建议使用JDK提供的三种常见的创建方式。因为:

  1. Executors提供的线程池使用场景很有限,一般场景很难用到。

  2. 这些线程池都是通过ThreadPoolExecutor创建的,通过直接使用ThreadPoolExecutor创建线程池,可以理解原理,灵活度更高。

  3. Executors提供的线程池使用Linked队列,这个接近于无界,非常大,这样会堆积大量的请求,从而导致OOM,因此阿里巴巴开发手册推荐使用ThreadPoolExecutor创建线程池。

因此,在实际使用线程池的时候,需要根据实际情况选择合适的线程池类型,并使用ThreadPoolExecutor类进行创建和管理。同时,需要合理设置线程池参数,避免因线程池造成的性能问题。

🍊 数据结构

线程池的内部实现是通过一个线程池类 ThreadPoolExecutor 来完成的。ThreadPoolExecutor 具有下列重要的特征:

  • corePoolSize:核心线程数大小

  • maximumPoolSize:最大线程数

  • workQueue:线程池的任务队列

  • keepAliveTime:空闲线程等待新任务的超时时间

  • unit:keepAliveTime 的时间单位

  • threadFactory:用于创建新线程的工厂

  • handler:当添加任务失败时的策略

线程池中核心的数据结构是任务队列,用于存储已经提交的但尚未被执行的任务,亦可被视为缓冲。在 Java 中,线程池的任务队列支持多种类型的实现:

  • ArrayBlockingQueue:基于数组的有界队列,先进先出原则,当队列满了,就需要等待,直到有空位置。

  • LinkedBlockingQueue:基于链表的无界队列,先进先出原则,如果任务数大于核心线程数时,任务就会被放入任务队列,等待核心线程执行完毕后再执行。

  • PriorityBlockingQueue:基于优先级的无界队列,通过任务的优先级来进行排序。

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。

线程池在执行任务时,通过选择合适的数据结构实现了多线程的调度、线程的复用,从而提高了应用程序的执行效率和线程使用效率。

🍊 线程池拒绝策略

线程池的拒绝策略是指在线程池的工作队列已满并且线程池中的线程达到最大数时,如何处理新提交的任务。不同的拒绝策略可以提高系统的稳定性和可靠性。

以下是线程池中的四种拒绝策略:

🎉 1. AbortPolicy拒绝策略

AbortPolicy是线程池默认的拒绝策略,也是最简单的拒绝策略。当线程池的工作队列已满并且线程池中的线程达到最大数时,此时再次提交任务将会直接抛出RejectedExecutionException异常,从而阻止系统正常运行。这种拒绝策略适用于临时任务提交的情况,例如在一个定时任务中,每次处理完任务后,需要再次提交一个新的任务。当达到最大限制时,这些任务可以被忽略,因为它们并不是必要的,只是一些额外的工作。

🎉 2. CallerRunsPolicy拒绝策略

CallerRunsPolicy是一种比较友好的拒绝策略。当线程池的工作队列已满并且线程池中的线程达到最大数时,此时再次提交任务将会将某些任务回退到调用者,让提交任务的线程来执行。也就是说,当线程池无法处理当前任务时,任务的执行权会交给提交任务的线程来执行。这种策略可以有效地降低新任务的流量,并保留提交任务的线程的执行权。如果任务比较耗时,提交任务的线程也会处于忙碌状态,无法继续提交任务。因此,这种策略可以减缓任务的提交速度,使得线程池中的线程有足够的时间来消化任务。从长远来看,这也会提高系统的稳定性和可靠性。

🎉 3. DiscardOldestPolicy拒绝策略

DiscardOldestPolicy是一种直接抛弃队列中等待时间最长的任务的拒绝策略。当线程池的工作队列已满并且线程池中的线程达到最大数时,此时再次提交任务将会抛弃队列中等待时间最长的任务,然后将当前任务加入队列尝试再次提交当前任务。这种策略可以有效地防止队列中的任务过多,从而保证新任务的正常提交。但是,抛弃最老的任务可能会有一定的风险,因为这些任务可能非常重要,并且被抛弃可能会对系统的稳定性和可靠性造成影响。

🎉 4. DiscardPolicy拒绝策略

DiscardPolicy是一种直接丢弃任务的拒绝策略,不会对提交任务进行任何处理或者抛出异常。当任务被提交时,直接将刚提交的任务丢弃,而且不会给与任何提示通知。这种策略虽然简单,但是对于一些重要的任务而言,可能会产生非常严重的后果。因此,如果任务的重要性比较高,不建议使用此策略。

综上所述,针对不同的业务需求,我们可以选择不同的线程池拒绝策略来保证系统的稳定性和可靠性。一般情况下,我们可以采用CallerRunsPolicy策略来保证任务不被丢弃,并将这些任务交给提交任务的线程来执行。如果任务量过大,可能需要采用DiscardOldestPolicy策略来丢弃等待时间最长的任务,从而保证新任务的正常提交。如果任务的重要性较低,我们可以使用DiscardPolicy策略来直接丢弃任务,但在处理重要任务时,不建议使用此策略。最后,需要提醒的是,在使用线程池时,一定要根据具体情况进行配置,以保证系统的稳定性和可靠性。

🍊 实战使用场景

线程池在大多数并发场景中都得到了广泛的应用,比如服务器接收大量的请求、webservice 接口,对于耗时的后台比如支付,发送短信等等都可以使用线程池来异步处理。在异步处理的同时,还可以提高响应速度和性能,防止服务器超负荷而崩溃。

另外,在一些需要随时处理任务的系统中,比如数据库连接池、消息队列、定时任务操作等,一般都会使用线程池,以达到高效稳定的目的。

🍊 遇到的问题和解决方案

  1. 线程池设置不当导致OOM问题

当线程池中的任务数量过多时,线程池可能会引发内存溢出(OOM)问题。此时,可以调整线程池的参数来解决该问题,例如增加空闲线程的存活时间、调整任务队列的大小等。

  1. 线程池拒绝策略导致任务执行失败

当线程池中的任务数量达到最大值时,线程池会按照预设的拒绝策略来处理新提交的任务,这有可能会导致一些任务无法被执行,从而影响应用程序的正常运行。为避免该问题,可以考虑调整拒绝策略,例如使用 DiscardOldestPolicy 策略丢弃任务队列中等待最久的任务。

  1. 线程池的大小设置不合理导致系统性能下降

线程池的大小设置不当会导致系统性能下降。如果线程池过小,那么任务就需要排队等待,从而影响系统的响应时间;而如果线程池过大,那么就会导致线程的上下文切换频繁,从而浪费系统资源。为避免该问题,可以根据系统的特点和负载情况,调整线程池的大小和核心线程数。

  1. 没有及时关闭线程池导致系统性能下降

当系统不再需要使用线程池时,如果没有及时关闭线程池,那么就会占用系统资源,从而导致系统性能下降。为避免该问题,应该在不需要使用线程池时,及时关闭线程池,释放系统资源。

  1. 线程池中出现死锁问题

当线程池中的任务存在相互依赖的关系时,可能会发生死锁问题。此时,可以通过合理的设计任务之间的依赖关系,或者通过使用并发控制工具类来解决该问题。

  1. 线程池中的任务出现异常问题

当线程池中的任务出现异常问题时,应该及时处理异常,避免线程池中的其他任务受到影响。可以通过设置 Thread.UncaughtExceptionHandler 或者使用 try-catch 块来捕获异常并处理。

以下是代码示例,展示如何处理线程池中的异常问题:

// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交任务
executor.submit(new Runnable() {
    @Override
    public void run() {
        // 任务逻辑代码
        try {
            // 执行任务代码
        } catch (Exception e) {
            // 捕获异常并处理
            // 输出异常信息
            System.out.println(e.getMessage());
            // 定义异常处理逻辑
            // ...
        }
    }
});

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

以上代码示例中,我们通过 ExecutorService 来创建线程池,并通过 submit() 方法来提交任务。在任务中,使用 try-catch 块来捕获异常,然后在 catch 块中进行异常处理。最后,使用 shutdown() 方法来关闭线程池。这样可以保证线程池中的任务在出现异常时能够被及时处理,从而避免影响其他任务的执行。

🍊 如何合理的配置核心线程数?

在实际开发中,我们经常会遇到需要执行大量任务的场景,例如同时处理多个请求、并发下载等。为了提高任务执行效率,极大降低线程创建和销毁的开销,我们通常会使用线程池来管理线程。但是,线程池的设置并不是一成不变的,需根据具体情况进行选择和优化。

对于CPU密集型任务,因为这类任务通常需要大量的计算,而计算过程中需要 CPU 不停地工作,不需要进行 I/O 操作,因此这类任务需要较少的线程数,否则容易引起上下文切换的过度消耗。因此,推荐将线程池的核心线程数设置为 CPU 核心数加 1。这样能够充分利用 CPU 的性能,提高任务执行效率,减少线程创建和销毁的开销。同时,也可以最大程度地减小上下文切换的次数,保证 CPU 的高效运行。

对于I/O密集型任务,因为这类任务需要频繁进行 I/O 操作,而这些操作通常需要等待一定的时间才能完成,因此线程的 CPU 使用率并不高。在这种情况下,可以通过增加线程数来提高并发度,让 CPU 在等待 I/O 操作的时候去处理别的任务,充分利用 CPU。根据经验,通常建议将线程池的核心线程数设置为 2 * CPU 核心数。

对于混合型任务,需要考虑任务的具体情况。如果任务包含了大量的 I/O 操作,那么可以根据 I/O 密集型任务的设置参数进行设置;如果任务主要是 CPU 密集型,那么可以根据 CPU 密集型任务的设置参数进行设置。

最后,需要指出的是,线程池的设置并不是万能的。对于并发高、业务执行时间长的任务,可以从整体架构的设计入手,看看业务中某些数据是否能做缓存,是否有必要增加服务器等措施。如果任务执行时间过长,还可以考虑使用中间件对任务进行拆分和解耦,以提高效率和稳定性。总之,在实际开发中,需要根据具体情况进行选择和优化,才能充分发挥线程池的优势。

🍊 底层运行原理

线程池是一种重要的多线程编程技术,它可以在处理任务时有效地管理线程数量,提高系统的运行效率和性能。线程池的核心思想是通过维护一个线程队列和一个任务队列,来有效地分配和调度线程资源,实现高效的并发处理。

线程池的设计思想可以类比为银行网点,其中常驻核心数相当于银行的今日当值窗口。这些常驻线程可以持续进行任务处理,以保证系统的基本运行能力。当任务数量超过了常驻核心数时,线程池会将多余的任务放入任务队列中等待处理。此时,线程池会根据需要动态地创建新的线程来处理任务,以满足系统的负载需求。这些新创建的线程可以在任务处理完成后立即销毁,从而节省系统资源。

线程池能够同时执行的最大线程数相当于银行的所有窗口数量。当任务队列中的任务超过了线程池能够处理的数量时,多余的任务将会被排队等待。这个过程就类似于银行的候客区,等待着客户能够进入银行。当线程池中的所有线程都在处理任务时,如果有新的任务需要处理,这些任务就会被放入任务队列中,等待空闲的线程来处理。

线程池的一个重要特性就是它能够处理突发性的任务请求。当有大量的任务需要同时处理时,线程池可以动态地创建新的线程,以满足系统的需求。如果线程池中线程的数量超过了最大限制,那么多余的任务就会被放入任务队列中等待。这个过程就类似于银行的窗口数量不足时,银行会临时加开窗口来处理客户。

还有一个重要特性就是线程池的鲁棒性。线程池的鲁棒性是指它在面对各种异常情况时的稳定性和可靠性。线程池可以高效地管理和调度大量的线程,可以根据实际情况调整线程池的大小和其他参数,使得系统在高并发场景下依然能够稳定运行。同时,线程池还具备一定的容错能力,可以在单个线程出现异常或崩溃时自动重新创建线程,避免因为单个线程的问题导致整个系统崩溃。

比如说,小明是一家工厂的生产经理,他需要确保每天工厂生产线的所有设备都在正常运行,并及时处理任何可能导致设备故障的异常情况。他决定使用线程池的思想来管理工厂的设备。他先定义了一个线程池的大小和其他相关参数,然后将每个设备的运行状态都放入线程池中的任务队列中,让线程池自动管理和调度这些任务。如果某个设备出现异常,例如机器过热,线程池会自动分配其他线程来处理这个问题,而不会对整个任务队列造成影响。这样,小明可以高效地处理工厂设备的异常情况,确保工厂稳定运行,提高了工厂的生产效率。

线程池需要能够处理各种异常情况,比如线程执行过程中出现了异常、线程执行时间过长等等。这种情况就类似于银行窗口工作出现问题时,银行需要及时处理故障,防止系统崩溃。

在处理任务时,线程池还需要考虑线程的优先级问题。线程池可以根据任务的优先级来动态地调整线程的优先级,以保证任务能够按照优先级依次被处理。这个过程就类似于银行对客户进行优先级排序,以保证优先处理重要客户的需求。

线程池的优化也是一个不断迭代的过程。对于线程池的设计来说,需要考虑到很多方面的因素,比如线程的创建和销毁、任务的调度和分配、线程的安全性和性能问题等等。只有综合考虑这些因素,才能够设计出高效、稳定、可靠的线程池系统。

总之,线程池是一种非常重要的多线程编程技术,能够有效地提高系统的运行效率和性能。在实际应用中,我们需要根据具体的需求来对线程池进行优化和改进,以满足不同的系统需求。

CSDN

🔔如果您需要转载或者搬运这篇文章的话,非常欢迎您私信我哦~

希望各位读者大大多多支持用心写文章的博主,现在时代变了,信息爆炸,酒香也怕巷子深,博主真的需要大家的帮助才能在这片海洋中继续发光发热,所以,赶紧动动你的小手,点波关注❤️,点波赞👍,点波收藏⭐,甚至点波评论✍️,都是对博主最好的支持和鼓励!

📥博主的人生感悟和目标

探寻内心世界,博主分享人生感悟与未来目标

  • 🍋程序开发这条路不能停,停下来容易被淘汰掉,吃不了自律的苦,就要受平庸的罪,持续的能力才能带来持续的自信。我本身是一个很普通程序员,放在人堆里,除了与生俱来的盛世美颜,就剩180的大高个了,就是我这样的一个人,默默写博文也有好多年了。
  • 📺有句老话说的好,牛逼之前都是傻逼式的坚持,希望自己可以通过大量的作品、时间的积累、个人魅力、运气、时机,可以打造属于自己的技术影响力。
  • 💥内心起伏不定,我时而激动,时而沉思。我希望自己能成为一个综合性人才,具备技术、业务和管理方面的精湛技能。我想成为产品架构路线的总设计师,团队的指挥者,技术团队的中流砥柱,企业战略和资本规划的实战专家。
  • 🎉这个目标的实现需要不懈的努力和持续的成长,但我必须努力追求。因为我知道,只有成为这样的人才,我才能在职业生涯中不断前进并为企业的发展带来真正的价值。在这个不断变化的时代,我必须随时准备好迎接挑战,不断学习和探索新的领域,才能不断地向前推进。我坚信,只要我不断努力,我一定会达到自己的目标。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员廖志伟

赏我包辣条呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值