唠一唠Java线程池

news2024/11/13 10:05:49

第1章:引言

大家好,我是小黑,咱们今天来聊聊Java线程池,如果没有线程池,每个线程都需要手动创建和销毁线程,那将是多么低效和耗资源啊!

线程池的核心作用就是复用已创建的线程,减少系统开销,提高响应速度。咱们在开发高并发应用时,经常会遇到需要同时执行多个任务的场景,这时候线程池就闪亮登场了。它能够合理分配每个任务到线程,实现资源的最优使用。

但别小看了这个线程池,用得不好可是会出大问题的。比如,线程池大小配置不当,可能会导致系统崩溃,或者效率低下。所以,小黑今天就带大家深入浅出地探索Java线程池的奥秘,一起学习如何调优和监控它。

第2章:Java线程池概述

讲到线程池,咱们得先了解下Java里面线程池的基本构成。Java中的线程池主要依靠java.util.concurrent包里的ThreadPoolExecutor类来实现。它是一个强大的工具,可以帮助咱们有效地管理线程资源。

线程池的工作原理大概是这样的:有一个线程池管理器(ThreadPoolExecutor),负责创建和管理线程池;还有一个工作队列,用来存放待处理的任务;还有若干个工作线程,执行这些任务。

咱们先来看一段基础的线程池创建代码,小黑会一步一步解释:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            threadPool.execute(() -> {
                System.out.println("执行任务:" + taskId + ",线程名:" + Thread.currentThread().getName());
            });
        }
        
        threadPool.shutdown(); // 关闭线程池
    }
}

这段代码创建了一个固定大小为5的线程池。Executors.newFixedThreadPool(5)这一行代码就完成了这个魔法。然后,咱们通过一个循环创建了10个任务,通过threadPool.execute()方法提交到线程池中执行。每个任务只是简单地打印出它的任务ID和执行它的线程名。

注意到了吗?这里咱们使用了shutdown()方法来关闭线程池。这是因为线程池用完之后,如果不关闭,那么它里面的线程会一直处于等待状态,这样会导致资源浪费。

第3章:线程池的核心参数解析

1. ThreadPoolExecutor的关键参数

当咱们创建一个线程池的时候,通常会遇到几个关键的参数,它们决定了线程池的行为和性能:

  • corePoolSize(核心线程数): 这个参数表示线程池中常驻的线程数量。即使线程空闲,线程池也不会释放这些线程。
  • maximumPoolSize(最大线程数): 线程池能容纳的最大线程数。当工作队列满了之后,线程池会创建新线程,直到达到这个上限。
  • keepAliveTime(线程保持活动时间): 当线程数超过核心线程数时,这是超出部分线程在空闲时的存活时间。
  • unit(时间单位): keepAliveTime的时间单位。
  • workQueue(工作队列): 存放待处理任务的队列。它通常是一个BlockingQueue的实现类。
  • threadFactory(线程工厂): 用于创建新线程的工厂。
  • handler(拒绝策略): 当线程池和工作队列都满了,如何处理新提交的任务。
2. 参数设置实例

来看一下如何在实际代码中设置这些参数:

import java.util.concurrent.*;

public class ThreadPoolParameterDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            5, // 核心线程数:5
            10, // 最大线程数:10
            1, // 空闲线程存活时间:1分钟
            TimeUnit.MINUTES, // 时间单位:分钟
            new LinkedBlockingQueue<>(10), // 工作队列大小:10
            Executors.defaultThreadFactory(), // 默认线程工厂
            new ThreadPoolExecutor.AbortPolicy()); // 拒绝策略:直接抛出异常

        // 使用线程池执行任务的代码...
        threadPool.shutdown();
    }
}

这段代码创建了一个自定义的线程池。核心线程数设置为5,最大线程数是10,如果线程池中线程数量超过核心线程数,多余的线程空闲1分钟后会被回收。工作队列的容量是10,超过这个数目的任务会导致线程池增加线程,直到达到最大线程数。如果线程池和队列都满了,新提交的任务将会触发拒绝策略,在这个例子中是直接抛出异常。

3. 参数调整的影响

调整这些参数会对线程池的性能产生显著影响。例如,如果corePoolSizemaximumPoolSize设置得过大,可能会导致线程数量过多,消耗大量系统资源,甚至引发内存溢出。反之,如果设置得过小,可能无法充分利用系统资源,降低任务处理速度。

同样,keepAliveTime和工作队列的大小也需要根据具体的场景来调整。一个合理的设置可以让线程池既不浪费资源,又能高效地处理任务。

第4章:线程池调优策略

调优的关键点
  1. 了解应用的需求:是CPU密集型还是IO密集型?任务是长期运行还是短暂的?
  2. 合理设置核心和最大线程数:根据任务类型和数量调整这两个参数。
  3. 选择适合的工作队列:根据任务处理速度和队列大小合理选择。
  4. 合理配置线程存活时间:调整keepAliveTime以优化资源使用。
  5. 监控线程池的状态:通过日志或者监控工具,持续观察线程池的运行状况。
调优实例

假设小黑正在开发一个Web服务,这个服务主要处理一些短暂的HTTP请求。大部分情况下,这些请求都是IO密集型的,也就是说,线程大部分时间都在等待网络传输。那么,咱们应该怎么调整线程池的参数呢?

来看一下代码示例:

import java.util.concurrent.*;

public class WebServiceThreadPool {
    public static void main(String[] args) {
        // IO密集型任务,可以适当增加最大线程数
        int corePoolSize = Runtime.getRuntime().availableProcessors(); // 核心线程数设置为CPU核心数
        int maximumPoolSize = corePoolSize * 2; // 最大线程数设置为核心线程数的两倍

        ThreadPoolExecutor webServiceThreadPool = new ThreadPoolExecutor(
            corePoolSize, 
            maximumPoolSize,
            60L, TimeUnit.SECONDS, // 空闲线程存活时间:60秒
            new SynchronousQueue<>(), // 适合短任务的队列
            Executors.defaultThreadFactory(), 
            new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略:由调用线程处理该任务

        // 使用线程池处理任务的代码...
        webServiceThreadPool.shutdown();
    }
}

在这个例子中,核心线程数设置为CPU的核心数,因为IO密集型任务不会一直占用CPU。最大线程数是核心线程数的两倍,可以在高峰时分担更多任务。由于任务短暂,使用SynchronousQueue作为工作队列,这样一旦有任务就立即执行。线程的存活时间设置为60秒,避免频繁地创建和销毁线程。

调优是个细活儿,需要根据实际情况来。比如,如果是CPU密集型任务,最大线程数就不宜设置太高。而且,调优不是一劳永逸的,随着应用的发展,可能需要不断调整。

调优线程池是个技术活,也是个经验活。需要咱们不断实践、观察和调整。记得,持续监控线程池的状态是非常重要的。

第5章:线程池监控的必要性和方法

为什么需要监控线程池
  1. 及时发现问题:通过监控,可以及时发现线程池的性能瓶颈,比如线程饥饿、任务拥堵等。
  2. 调优依据:监控数据可以为线程池的调优提供重要依据,帮助咱们更好地理解线程池的行为。
  3. 预防系统崩溃:适时的监控可以防止因线程池配置不当导致的系统崩溃。
监控线程池的关键指标
  • 线程数量:包括核心线程数、活跃线程数、最大线程数。
  • 任务队列长度:了解队列中等待执行的任务数量。
  • 任务完成数:监控已经完成的任务数量,了解线程池的工作量。
  • 拒绝任务数:被拒绝的任务数量,这个很重要,反映了线程池的饱和度。
实现线程池监控的代码示例

来看一段Java代码,展示如何实现线程池的基本监控:

import java.util.concurrent.*;

public class ThreadPoolMonitorDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            5, // 核心线程数
            10, // 最大线程数
            1, TimeUnit.SECONDS, // 线程保持活动时间
            new LinkedBlockingQueue<>(5)); // 工作队列

        // 提交一些任务到线程池
        for (int i = 0; i < 15; i++) {
            int taskId = i;
            threadPool.execute(() -> {
                try {
                    Thread.sleep(100); // 模拟任务执行时间
                    System.out.println("执行任务:" + taskId);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // 定期监控线程池状态
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            System.out.println("====== 线程池状态监控 ======");
            System.out.println("核心线程数:" + threadPool.getCorePoolSize());
            System.out.println("活跃线程数:" + threadPool.getActiveCount());
            System.out.println("最大线程数:" + threadPool.getMaximumPoolSize());
            System.out.println("队列任务数:" + threadPool.getQueue().size());
            System.out.println("完成任务数:" + threadPool.getCompletedTaskCount());
        }, 0, 1, TimeUnit.SECONDS); // 每秒监控一次

        Thread.sleep(5000); // 模拟运行一段时间
        threadPool.shutdown();
    }
}

在这段代码中,咱们创建了一个线程池,然后提交了一些任务。随后,使用一个单线程定时调度器来每秒打印一次线程池的状态,包括核心线程数、活跃线程数、最大线程数、队列中的任务数和完成的任务数。

通过这样的监控,咱们可以实时地了解线程池的健康状况。如果发现有异常,比如活跃线程数持续很高或者队列任务数骤增,那就需要及时调整线程池的配置或优化任务处理逻辑了。

第6章:案例研究:线程池的调优与监控

案例背景

假设小黑在负责一个在线购物网站的后端服务。这个服务需要处理大量的用户请求,比如商品浏览、订单处理等。由于访问量大,对性能的要求也高,因此使用线程池来提高效率和响应速度是必要的。

初始线程池配置

一开始,线程池的配置是这样的:

  • 核心线程数:8
  • 最大线程数:50
  • 工作队列长度:100
  • 线程保持活动时间:60秒
遇到的问题

随着网站流量的增加,后端服务开始出现响应缓慢的问题。通过监控发现,在高峰时段,线程池的活跃线程数经常达到最大值,队列中等待的任务数也在不断增加。

调优过程

小黑根据这个情况,决定对线程池进行调优。调优的主要目标是提高系统的吞吐量和响应速度。调优的步骤包括:

  1. 增加核心线程数和最大线程数:考虑到服务器的硬件资源允许,小黑把核心线程数提高到16,最大线程数提高到100。
  2. 调整工作队列长度:为了减少任务等待时间,小黑把工作队列的长度减少到50。
  3. 优化线程保持活动时间:将线程的保持活动时间调整为30秒,以便在不那么繁忙时能更快地释放资源。
调优后的结果

调优后,系统的整体性能有了显著提升。活跃线程数更加平稳,队列中等待的任务数量也大幅减少。响应时间缩短,用户体验得到了改善。

代码示例

这里有一段模拟调优后线程池配置的代码:

import java.util.concurrent.*;

public class OptimizedThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            16, // 核心线程数:16
            100, // 最大线程数:100
            30L, TimeUnit.SECONDS, // 线程保持活动时间:30秒
            new LinkedBlockingQueue<>(50)); // 工作队列长度:50

        // 提交任务到线程池的代码...
        threadPool.shutdown();
    }
}

在这段代码中,线程池的配置更适合高并发的Web服务场景。核心线程数和最大线程数的提升,以及工作队列长度的调整,都是为了更好地适应用户请求的高峰。

第7章:总结

经过前面几章的深入探讨,咱们已经对Java线程池有了一个全面的了解。从基本概念到调优监控,小黑希望这些内容能帮助大家在实际工作中更好地使用线程池。

  1. 理解核心参数:核心线程数、最大线程数、工作队列等参数的合理配置对线程池的性能至关重要。
  2. 监控和调优:持续监控线程池的状态,并根据实际情况进行调优,是保证线程池高效运行的关键。
  3. 适应应用场景:根据具体的应用需求(如CPU密集型、IO密集型)来定制线程池。
  4. 性能优化:在高并发场景下,性能优化是提高应用性能的重要手段。

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

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

相关文章

什么是MOM,与MES系统的差异是什么

MOM基本概念介绍 由于绝大多数MES只关注生产执行的核心作用, 对维护管理、质量管理和库存管理的重视程度有限,有时甚至缺少这部分功能, 并且未提高到与生产运营相类似的复杂程度, 难以充分满足现代制造企业对其制造运营区域的业务管理需求, 进而直接影响对企业的运营管理效果。…

自带恒压恒流环路的降压型单片车充专用芯片

一、基本概述 XL2009是一款高效降压型DC-DC转换器&#xff0c;固定180KHz开关频率&#xff0c;可以提供最高2.5A输出电流能力&#xff0c;具有低纹波&#xff0c;出色的线性调整率与负载调整率特点。XL2009内置固定频率振荡器与频率补偿电路&#xff0c;简化了电路设计。 PWM …

AArch64 memory management学习(二)

提示 该博客主要为个人学习&#xff0c;通过阅读官网手册整理而来&#xff08;个人觉得阅读官网的英文文档非常有助于理解各个IP特性&#xff09;。若有不对之处请参考参考文档&#xff0c;以官网文档为准。AArch64 memory management学习一共分为两章&#xff0c;这是第二章。…

Transformer从菜鸟到新手(五)

引言 上篇文章我们在单卡上完成了完整的训练过程。 从本文开始介绍模型训练/推理上的一些优化技巧&#xff0c;本文主要介绍多卡并行训练。 下篇文章将介绍大模型推理常用的缓存技术。 多卡训练 第一个要介绍的是利用多GPU优化&#xff0c;因为在单卡上训练实在是太慢。这…

ORACLE索引失效和sql优化

全部都是在开发中碰到的真实问题&#xff0c;后续会一直更新本条帖子&#xff1a; 场景一&#xff1a; 使用了函数&#xff0c;导致索引失效 交易日期过滤数据时使用了to_date写法&#xff0c;导致日期字段查询未走索引 将控制台输出的sql 粘贴到plsql 按F5打开解释计划执行窗口…

【Android】 ConstraintLayout实操

由于最近比较悠闲&#xff0c;重新学习了constraintlayout&#xff0c;看着官网学的&#xff0c;官网网站如下&#xff1a;https://developer.android.com/training/constraint-layout?hlzh-cn#alignment 其实之前也小小的学过一波constraintlayout&#xff0c;不过因为用线性…

C#,入门教程(13)——字符(char)及字符串(string)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(12)——数组及数组使用的基础知识https://blog.csdn.net/beijinghorn/article/details/123918227 字符串的使用与操作是必需掌握得滚瓜烂熟的编程技能之一&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; C#语言实…

Copilot 插件的使用介绍:如何快速上手

GitHub Copilot 本文主要介绍如何通过脚本工具激活 GitHub Copilot 插件&#xff0c;提供安装及激活图文教程&#xff0c;大家按下面操作即可激活GitHub Copilot插件&#xff0c;免费使用Ai编码工具 一、GitHub Copilot 介绍 GitHub Copilot 是由 GitHub 和 OpenAI 共同开发的…

Namp端口扫描

在 CentOS 7 上安装 Nmap 的步骤如下&#xff1a; 打开终端&#xff1a; 作为 root 用户&#xff0c;您已经具备执行安装命令的权限。 使用 Yum 安装 Nmap&#xff1a; 在终端中&#xff0c;输入以下命令来安装 Nmap&#xff1a;sudo yum install nmap如果您已经是 root 用户&…

浅谈配电能源管理系统应用在钢铁行业

叶根胜 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;能源管理系统在钢铁行业的应用不仅是钢铁行业信息化、数据化、智能化发展的重要体现&#xff0c;也是钢铁行业实现智能制造和精细化管理的重要举措。有鉴于此&#xff0c;从能源管理系统在钢铁行业的应用意义…

计算机体系结构超标量及分支预测学习记录

1.CPIIdeal CPI Structural stalls Data hazard stalls Control stalls 超标量通过动态调度&#xff0c;进一步降低CPI&#xff0c;通过每个周期发射多条指令来执行 2.猜测执行就是为了在Tomasulo算法的基础上&#xff0c;进一步解决控制冲突&#xff1b; 猜测执行算法与T…

Python(32):字符串转换成列表或元组,列表转换成字典小例子

1、python 两个列表转换成字典 字符串转换成列表 列表转换成字典 column "ID,aes,sm4,sm4_a,email,phone,ssn,military,passport,intelssn,intelpassport,intelmilitary,intelganghui,inteltaitonei,credit_card_short,credit_card_long,job,sm4_cbc,sm4_a_cbc" …

Vue入门三(表单控制|购物车案例|v-model进阶|与后端交互)

文章目录 一、表单控制二、购物车案例三、v-model进阶四、与后端交互跨域问题解决&#xff0c;三种交互方法跨域问题详解1-CORS&#xff1a;后端代码控制&#xff0c;上面案例采用的方式1) 方式一&#xff1a;后端添加请求头2) 方式二&#xff1a;编写中间件3) 方式三&#xff…

前端下载文件问题之如何获取报错信息

问题&#xff1a;点击下载后。接口会生成并返回文件流。在极端情况下接口数据返回异常&#xff0c;需要抛出错误信息&#xff0c;比如后端拼接错误情况、空文件情况。 难点&#xff1a;responseType设置为Blob后&#xff0c;返回内容为二进制文件流&#xff0c;从而无法获取错误…

抓包神技--DPDK

DPDK&#xff0c;全称Data Plane Development Kit&#xff0c;是一个高性能的数据包处理工具集。估计有不少朋友使用过或者之前了解过&#xff0c;它通过绕过Linux内核协议栈&#xff0c;直接在用户空间进行数据包处理&#xff0c;大大提高了数据包处理的效率和吞吐量。 DPDK主…

软件测试|MySQL ORDER BY详解:排序查询的利器

简介 在数据库中&#xff0c;我们经常需要对查询结果进行排序&#xff0c;以便更好地展示数据或满足特定的业务需求。MySQL提供了ORDER BY子句&#xff0c;使我们能够轻松地对查询结果进行排序。本文将详细介绍MySQL ORDER BY的用法和示例&#xff0c;帮助大家更好地理解和应用…

旋变检测AD2s1205手册学习笔记

旋变故障检测故障表 信号丢失检测 检测原理&#xff1a;任一旋变输入(正弦或余弦)降至指定的LOS正弦/余弦阈值 以下时&#xff0c;器件会检测到信号丢失(LOS)。AD2S1205通过将 监视信号与固定最小值进行比较检测此点 丢失的效果表现&#xff1a;LOS由DOS和LOT引脚均闩锁为逻辑…

Linux系统下gitee使用git提交代码

Linux系统下gitee使用git提交代码 一、安装配置git1.1 在 Linux 中安装 git&#xff0c;并生成授信证书1.2 将SSH key 添加到 ssh-agent1.2 将SSH key 添加到你的gitee账户 二、gitee 的使用2.1 下载项目到本地 三、上传gitee三步走3.1 三板斧第一招&#xff1a;git add3.2 三板…

vivado 导入工程、TCL创建工程命令、

导入外部项目 您可以使用导入在Vivado IDE外部创建的现有RTL级项目文件Synopsys Synplify。Vivado IDE检测项目中的源文件并自动添加文件到新项目。设置&#xff0c;如顶部模块、目标设备和VHDL库 分配是从现有项目导入的。 1.按照创建项目中的步骤进行操作。 2.在“项目类…

MySQL语法及IDEA使用MySQL大全

在项目中我们时常需要写SQL语句&#xff0c;或简单的使用注解直接开发&#xff0c;或使用XML进行动态SQL之类的相对困难的SQL&#xff0c;并在IDEA中操控我们的SQL&#xff0c;但网上大都图方便或者觉得太简单了&#xff0c;完全没一个涵盖两个方面的讲解。 单表&#xff1a; …