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;
    }

    @Override
    public 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();
        }
    }

    @Override
    public 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 {
    @Override
    public 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

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

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

相关文章

iOS 自动签名打包,并用脚本上传appstore

背景&#xff1a; 1&#xff09;测试环境给测试&#xff0c;产品&#xff0c;或者其他业务人员打测试包时&#xff0c;经常存在需要添加设备&#xff0c;不得不重新生成描述文件&#xff0c;手动去更新打包机描述文件配置 2&#xff09;证书&#xff0c;描述文件过期造成打包失…

Lag-Llama:基于 LlaMa 的单变量时序预测基础模型

文章构建了一个通用单变量概率时间预测模型 Lag-Llama&#xff0c;在来自Monash Time Series库中的大量时序数据上进行了训练&#xff0c;并表现出良好的零样本预测能力。在介绍Lag-Llama之前&#xff0c;这里简单说明什么是概率时间预测模型。概率预测问题是指基于历史窗口内的…

目标检测算法改进系列之添加变核卷积AKConv模块

AKConv变核卷积 KConv的主要思想&#xff1a;AKConv&#xff08;可变核卷积&#xff09;主要提供一种灵活的卷积机制&#xff0c;允许卷积核具有任意数量的参数和采样形状。这种方法突破了传统卷积局限于固定局部窗口和固定采样形状的限制&#xff0c;从而使得卷积操作能够更加…

【栈和队列(2)】

文章目录 前言队列队列方法队列模拟实现循环队列练习1 队列实现栈 前言 队列和栈是相反的&#xff0c;栈是先进后出&#xff0c;队列是先进先出&#xff0c;相当于排队打饭&#xff0c;排第一的是最先打到饭出去的。 队列 队列&#xff1a;只允许在一端进行插入数据操作&…

Python练习题(二)

&#x1f4d1;前言 本文主要是【Python】——Python练习题的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&am…

Pytest测试攻略:探寻pytest.main()隐藏的利器

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Pytest测试框架中&#xff0c;pytest.main()是一个重要的功能&#xff0c;用于启动测试执行。它允许以不同方式运行测试&#xff0c;传递参数和配置选项。本文将深入探讨pytest.main()的核心功能&#xff0c;提…

栈和队列OJ题——15.循环队列

15.循环队列 622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; * 解题思路&#xff1a; 通过一个定长数组实现循环队列 入队&#xff1a;首先要判断队列是否已满&#xff0c;再进行入队的操作&#xff0c;入队操作需要考虑索引循环的问题&#xff0c;当索引越界&…

Qt/QML编程学习之心得:如何添加资源文件到QML工程(十一)

Qt作为一种GUI界面编辑工具&#xff0c;在嵌入式编程中也大受欢迎&#xff0c;而进一步QML出现了&#xff0c;QML我理解也是一种资源文件&#xff0c;因为像其他资源文件一样添加进工程的。那么一个图片如何增加进资源文件呢&#xff1f;这个的确很基础&#xff0c;就是把资源文…

IdleStateHandler 心跳机制源码详解

优质博文&#xff1a;IT-BLOG-CN 一、心跳机制 Netty支持心跳机制&#xff0c;可以检测远程服务端是否存活或者活跃。心跳是在TCP长连接中&#xff0c;客户端和服务端定时向对方发送数据包通知对方自己还在线&#xff0c;保证连接的有效性的一种机制。在服务器和客户端之间一…

C++实现DFS、BFS、Kruskal算法和Prim算法、拓扑排序、Dijkstra算法

背景&#xff1a; 实现要求&#xff1a; 根据图的抽象数据类型的定义&#xff0c;请采用邻接矩阵来存储图1&#xff0c;采用邻接表来存储图2&#xff0c;并完成如下操作&#xff1a;对图1无向图进行深度优先遍历和广度优先遍历。对图1无向图采用Kruskal算法和Prim算法得出最小…

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

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

c语言-归并排序

目录 1、归并排序基本思想 2、归并排序的实现&#xff08;递归法&#xff09; 2.1 代码实现递归法归并排序 3、归并排序的实现&#xff08;非递归法&#xff09; 3.1 修正边界问题 3.2 代码实现非递归法归并排序 结语&#xff1a; 前言&#xff1a; 归并排序是一种把数…

万界星空科技灯具行业MES介绍

中国是LED照明产品最大的生产制造国&#xff0c;如今&#xff0c;我国初步形成了包括LED外延片的生产、LED芯片的制备、LED芯片的封装以及LED产品应用在内的较为完超为产业链&#xff0c;随着LED照明市场渗诱率的快速警升&#xff0c;LED下游应用市场将会越来越广阔。这也将推动…

硬件基础:二极管

基本定义 二极管的内部其实就是一个PN结。 把PN结封装起来&#xff0c;两边加上两个电极&#xff0c;就组成了半导体二极管。简称二极管&#xff08;Diode&#xff09; 二极管和PN结一样&#xff0c;具有单向导通性&#xff1a; 外观和正负极 常见芯片封装如下&#xff1a; 一般…

MDETR 论文翻译及理解

题目Abstract1. Introduction2. Method2.1. Background2.2. MDETR2.2.1 Architecture2.2.2 Training 3. Experiments3.1. Pre-training Modulated Detection 预训练调制检测3.2. Downstream Tasks3.2.1 Few-shot transfer for long-tailed detection 4. Related work5. Conclus…

飞行员兄弟

飞行员兄弟 思路&#xff1a; 这里一共有16个格子&#xff0c;如果暴力的话也就是2^16次方种排列组合。 这题和之前的开关不一样&#xff0c;这题是会影响到周围很多格子&#xff0c;而开关那题可以利用上方只改变一个的操作来解题&#xff0c;这题我想到的就是暴搜&#xff…

Prefix-Tuning 论文概述

Prefix-Tuning 论文概述 前缀调优&#xff1a;优化生成的连续提示前言摘要论文十问实验数据集模型实验结论摘要任务泛化性能 前缀调优&#xff1a;优化生成的连续提示 前言 大规模预训练语言模型(PLM)在下游自然语言生成任务中广泛采用fine-tuning的方法进行adaptation。但是f…

Redis 数据结构详解

分类 编程技术 Redis 数据类型分为&#xff1a;字符串类型、散列类型、列表类型、集合类型、有序集合类型。 Redis 这么火&#xff0c;它运行有多块&#xff1f;一台普通的笔记本电脑&#xff0c;可以在1秒钟内完成十万次的读写操作。 原子操作&#xff1a;最小的操作单位&a…

基于Java SSM框架实现实现四六级英语报名系统项目【项目源码+论文说明】

基于java的SSM框架实现四六级英语报名系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个高校四六级报名管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作…

HarmonyOS开发(九):数据管理

1、概述 1.1、功能简介 数据管理为开发者提供数据存储、数据管理能力。 它分为两个部分&#xff1a; 数据存储&#xff1a;提供通用数据持久化能力&#xff0c;根据数据特点&#xff0c;分为用户首选项、键值型数据库和关系型数据库。数据管理&#xff1a;提供高效的数据管…