【Java异步编程】基于任务类型创建不同的线程池

news/2025/2/2 23:23:33 标签: java, 开发语言

文章目录

    • 一. 按照任务类型对线程池进行分类
      • 1. IO密集型任务的线程数
      • 2. CPU密集型任务的线程数
      • 3. 混合型任务的线程数
    • 二. 线程数越多越好吗
    • 三. Redis 单线程的高效性

使用线程池的好处主要有以下三点:

  1. 降低资源消耗:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,通过重复利用已创建的线程可以降低线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
  3. 提高线程的可管理性:线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务的数量等。通过线程池可以对线程资源进行统一的分配、监控和调优。

虽然使用线程池的好处很多,但是如果其线程数配置得不合理,不仅可能达不到预期效果,反而可能降低应用的性能。接下来按照不同的任务类型来配置线程池。

 

一. 按照任务类型对线程池进行分类

使用标准构造器ThreadPoolExecutor创建线程池时,会涉及线程数的配置,而线程数的配置与异步任务类型是分不开的。这里将线程池的异步任务大致分为以下三类:

  1. IO密集型任务此类任务主要是执行IO操作。由于执行IO操作的时间较长,导致CPU的利用率不高,这类任务CPU常处于空闲状态。Netty的IO读写操作为此类任务的典型例子。
  2. CPU密集型任务此类任务主要是执行计算任务。由于响应时间很快,CPU一直在运行,这种任务CPU的利用率很高。
  3. 混合型任务此类任务既要执行逻辑计算,又要进行IO操作(如RPC调用、数据库访问)​。

相对来说,由于执行IO操作的耗时较长(一次网络往返往往在数百毫秒级别)​,这类任务的CPU利用率也不是太高。Web服务器的HTTP请求处理操作为此类任务的典型例子。一般情况下,针对以上不同类型的异步任务需要创建不同类型的线程池,并进行针对性的参数配置。

 

1. IO密集型任务的线程数

由于IO密集型任务的CPU使用率较低,导致线程空余时间很多,因此通常需要开CPU核心数两倍的线程。当IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。

java">@Slf4j  
//懒汉式单例创建线程池:用于IO密集型任务  
public class IoIntenseTargetThreadPoolLazyHolder {  
  
    /**  
     * IO线程池最大线程数  
     */  
    public static final int IO_MAX = Math.max(2, CPU_COUNT * 2);  
  
  
    /**  
     * 空闲保活时限,单位秒  
     */  
    public static final int KEEP_ALIVE_SECONDS = 30;  
  
  
    /**  
     * 有界队列size  
     */    
    public static final int QUEUE_SIZE = 10000;  
  
      
      
    //线程池: 用于IO密集型任务  
    public static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(  
            IO_MAX,  
            IO_MAX,  
            KEEP_ALIVE_SECONDS,  
            TimeUnit.SECONDS,  
            new LinkedBlockingQueue(QUEUE_SIZE),  
            new ThreadUtil.CustomThreadFactory("io"));  
  
    public static ThreadPoolExecutor getInnerExecutor() {  
        return EXECUTOR;  
    }  
  
    static {  
        log.info("线程池已经初始化");  
  
        EXECUTOR.allowCoreThreadTimeOut(true);  
        //JVM关闭时的钩子函数  
        Runtime.getRuntime().addShutdownHook(  
                new ShutdownHookThread("IO密集型任务线程池", new Callable<Void>() {  
                    @Override  
                    public Void call() throws Exception {  
                        //优雅关闭线程池  
                        shutdownThreadPoolGracefully(EXECUTOR);  
                        return null;  
                    }  
                }));  
    }  
}

 

有以下几点需要注意

  1. 调用allowCoreThreadTimeOut,传入了参数true,应用于核心线程,当池中的线程长时间空闲时,可以自行销毁。
  2. 使用有界队列缓冲任务而不是无界队列,如果128太小,可以根据具体需要进行增大,但是不能使用无界队列。
  3. corePoolSize和maximumPoolSize保持一致,使得在接收到新任务时,如果没有空闲工作线程,就优先创建新的线程去执行新任务,而不是优先加入阻塞队列,等待现有工作线程空闲后再执行。
  4. 使用JVM关闭时的钩子函数优雅地自动关闭线程池。

 

2. CPU密集型任务的线程数

CPU密集型任务也叫计算密集型任务,其特点是要进行大量计算而需要消耗CPU资源,比如计算圆周率、对视频进行高清解码等。

CPU密集型任务虽然也可以并行完成,但是并行的任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以要最高效地利用CPU,CPU密集型任务并行执行的数量应当等于CPU的核心数。

java">/**  
 * CPU核数  
 **/  
public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();  
  
public static final int MAXIMUM_POOL_SIZE = CPU_COUNT;  
  
//线程池: 用于CPU密集型任务  
private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(  
        MAXIMUM_POOL_SIZE,  
        MAXIMUM_POOL_SIZE,  
        KEEP_ALIVE_SECONDS,  
        TimeUnit.SECONDS,  
        new LinkedBlockingQueue(QUEUE_SIZE),  
        new CustomThreadFactory("cpu"));  
  
  
public static ThreadPoolExecutor getInnerExecutor() {  
    return EXECUTOR;  
}  
  
static {  
    log.info("线程池已经初始化");  
  
    EXECUTOR.allowCoreThreadTimeOut(true);  
    //JVM关闭时的钩子函数  
    Runtime.getRuntime().addShutdownHook(  
            new ShutdownHookThread("CPU密集型任务线程池", new Callable<Void>() {  
                @Override  
                public Void call() throws Exception {  
                    //优雅关闭线程池  
                    shutdownThreadPoolGracefully(EXECUTOR);  
                    return null;  
                }  
            }));  
}

 

3. 混合型任务的线程数

混合型任务既要执行逻辑计算,又要进行大量非CPU耗时操作(如RPC调用、数据库访问、网络通信等)​,所以混合型任务CPU的利用率不是太高,非CPU耗时往往是CPU耗时的数倍

比如在Web应用中处理HTTP请求时,一次请求处理会包括DB操作、RPC操作、缓存操作等多种耗时操作。一般来说,一次Web请求的CPU计算耗时往往较少,大致在100~500毫秒,而其他耗时操作会占用500~1000毫秒,甚至更多的时间。

在为混合型任务创建线程池时,如何确定线程数呢?业界有一个比较成熟的估算公式,具体如下:

java">
最佳线程数 = ((线程等待时间+线程CPU时间) / 线程CPU时间) * CPU核数

通过公式可以看出:等待时间所占的比例越高,需要的线程就越多;CPU耗时所占的比例越高,需要的线程就越少

 

下面举一个例子:

比如在Web服务器处理HTTP请求时,假设平均线程CPU运行时间为100毫秒,而线程等待时间(比如包括DB操作、RPC操作、缓存操作等)为900毫秒,如果CPU核数为8,那么根据上面这个公式,估算如下:

java">(900毫秒 + 100毫秒) / 100毫秒 * 8 = 10 * 8 = 80

 

二. 线程数越多越好吗

很多小伙伴认为,线程数越高越好。那么,使用很多线程是否就一定比单线程高效呢?答案是否定的。

虽然多线程在一些并发场景下能带来性能提升,但过多的线程并不意味着性能必定提升。线程数过高可能导致一些问题:

  • 上下文切换(Context Switching): 每个线程的执行都由操作系统调度,线程切换会带来额外的开销。当线程数过多时,操作系统频繁地在不同线程间切换,导致 上下文切换 成本增加,这样反而可能降低系统的整体效率。

  • 资源争用: 多线程同时访问共享资源时,可能会遇到 资源竞争锁竞争,特别是在 CPU 绑定的任务中。线程之间的协作和同步会称为性能瓶颈。

  • 内存开销: 每个线程需要占用一定的内存,维护线程栈、调度信息等,过多的线程会消耗大量的内存和系统资源,这可能会导致系统性能下降,甚至造成内存溢出

 

三. Redis 单线程的高效性

Redis 是一个 单线程 的高性能数据库,许多人可能会觉得它的设计不合常理,为什么不使用多线程来提升性能呢?然而,Redis 使用单线程反而能够达到极高的吞吐量,这是因为:

特点核心内容
1. 避免多线程上下文切换单线程模型避免了线程切换的开销,任务按顺序处理,简化了并发控制,避免了锁竞争和死锁问题。
2. 非阻塞设计采用事件驱动和 I/O 多路复用技术,非阻塞处理请求。如果一个请求需要等待外部资源(如网络 I/O),Redis 会把控制权交给其他请求,而不是阻塞线程。这种方式避免了多线程中因为等待 I/O 资源导致的线程空闲,充分利用了 CPU 的时间片。
3. CPU vs I/O 密集型Redis 的大多数操作(如 GET/SET)是 I/O 密集型 的,单线程在 I/O 密集型应用中有优势。
4. 数据访问模式Redis 操作主要是内存访问,内存操作速度快,单线程执行时没有同步问题,数据结构(如哈希表、跳表等)高效。

http://www.niftyadmin.cn/n/5840355.html

相关文章

为AI聊天工具添加一个知识系统 之79 详细设计之20 正则表达式 之7

本文要点 要点 “正则表达式” 本来是计算机科学计算机科学的一个概念。本项目将它推广&#xff08;扩张&#xff09;到认知科学的“认知范畴”概念&#xff0c; 聚合&#xff08;收敛&#xff09;到 神经科学 的“神经元”概念。 做法是&#xff1a;用reg 来系统化定义认知…

litemall,又一个小商场系统

litemall Spring Boot后端 Vue管理员前端 微信小程序用户前端 Vue用户移动端 代码地址&#xff1a;litemall: 又一个小商城。 litemall Spring Boot后端 Vue管理员前端 微信小程序用户前端 Vue用户移动端

数据结构:优先级队列—堆

一、优先级队列 1、优先级队列概念 优先级队列&#xff0c;听名字我们就知道他是一种队列&#xff0c;队列在前面我们已经学习过了&#xff0c;它是一种先进先出的数据结构&#xff0c;但是在特殊的情况下&#xff0c;我们我们队列中元素是带有一定优先级的&#xff0c;它需要…

SQL入门到精通 理论+实战 -- 在 MySQL 中学习SQL语言

目录 一、环境准备 1、MySQL 8.0 和 Navicat 下载安装 2、准备好的表和数据文件&#xff1a; 二、SQL语言简述 1、数据库基础概念 2、什么是SQL 3、SQL的分类 4、SQL通用语法 三、DDL&#xff08;Data Definition Language&#xff09;&#xff1a;数据定义语言 1、操…

kamailio中的sctp模块

以下是关于 Kamailio 配置中 enable_sctpno 的详细解释&#xff1a; 1. 参数作用 enable_sctp&#xff1a; 该参数用于控制 Kamailio 是否启用 SCTP&#xff08;Stream Control Transmission Protocol&#xff09; 协议支持。 设置为 yes&#xff1a;启用 SCTP&#xff0c;并加…

Nginx部署的前端项目刷新404问题

1&#xff0c;查看问题 我部署的81端口是监听tlias项目的&#xff0c;我直接访问端口页面可以出现内容。 我在浏览器舒服端口之后回车&#xff0c;会重定向页面。但是我在重定向之后的页面刷新浏览器就会出现404的问题。 下面是刷新浏览器后的效果 2&#xff0c;在nginx.cnf …

“新月智能武器系统”CIWS,开启智能武器的新纪元

新月人物传记&#xff1a;人物传记之新月篇-CSDN博客 相关文章链接&#xff1a;星际战争模拟系统&#xff1a;新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 “新月之智”智能战术头盔系统&#xff08;CITHS&#xff09;-CSDN博客 目录 智能武…

DeepSeek-R1 论文. Reinforcement Learning 通过强化学习激励大型语言模型的推理能力

论文链接&#xff1a; [2501.12948] DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning 实在太长&#xff0c;自行扔到 Model 里&#xff0c;去翻译去提问吧。 工作原理&#xff1a; 主要技术&#xff0c;就是训练出一些专有用途小模型&…