IO密集型任务设置线程池线程数实现方式

 更新时间:2024年07月04日 09:46:58   作者:疯狂佩奇  
这篇文章主要介绍了IO密集型任务设置线程池线程数实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

任务类型

CPU密集

CPU密集型的话,一般配置CPU处理器个数+/-1个线程,所谓CPU密集型就是指系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成。

IO密集

IO密集型的话,是指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理,即在这个时间范围内,可以由其他线程来使用CPU,因而可以多配置一些线程。(线程处于io等待或则阻塞状态时,不会占用CPU资源)

混合型

混合型的话,是指两者都占有一定的时间。

实际上工作中的大部分场景中,线程池的能力往往会超出想象。

测试准备

下面的计算方式很粗略,而且有漏洞,但是也可以作为一个参考

处理器信息

四核8线程 (超线程

任务示例

我们首先确认一下单个任务的io时间占比,下面是测试代码

class ThreadPoolTest {
 
    public static int PARK_TIME = 0;
 
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        runTask(1);
    }
 
    public static void runTask(int threadNum) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            threadNum, threadNum, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100)
        );
 
        long start = System.currentTimeMillis();
        List<Future<?>> taskList = new ArrayList<>();
        for (int i = 0; i < 1; i++) {
            taskList.add(threadPoolExecutor.submit(() -> {
                doJob();
            }));
        }
        for (Future<?> future : taskList) {
            future.get();
        }
        long time = System.currentTimeMillis() - start;
        System.out.println(threadNum + "个线程,耗时:" + time + "停顿占比" + (PARK_TIME * 100.0 / time));
        threadPoolExecutor.shutdown();
    }
 
 
    public static Long doJob() {
        long result = 0L;
        PARK_TIME = 0;
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            if (i % 10_000_000 == 0) {
                try {
                    ++PARK_TIME;
                    // 模拟IO
                    LockSupport.parkNanos(100_000_000);
                } catch (Exception ignore) {
                }
            }
            result += i;
        }
        return result;
    }
}

执行结果

直接运行 输出如下:

1个线程,耗时:27862停顿占比0.7716603258918958

也就是说大概77%的时间线程在睡觉。

分析

按我电脑的配置可以认为核心数coreNum为8, 假设任务数够多的情况下。

不考虑上下文切换等的耗时,单个任务io耗时占比为x,在线程数最少的情况下想让cpu利用率达到最高,可以得出一个等式 1 / (1 - x) * coreNum = 100% * coreNum(我们假设线程在活跃状态时能完全占用单个核心)。

代入上面得到的值 x = 0.77, coreNum = 8 可以比较容易的算出来如果想让cpu利用率达到最高, 1 / (1 - 0.77) * 8 约等于34。即线程池的线程数设置为35比较合理。

在我的电脑上合适的线程数和任务io耗时占比x的关系大致可以认为 1 / (1 - x) * 8,即理论上的图如下,这是一个非常粗糙的等式,实际上随着线程数增多,上下文切换带来的开销越来越大,和下面这张图的出入还是蛮大的。

不同线程数下的程序总执行耗时

下面简单修改下程序来验证一下任务量固定,不同线程数下的程序执行耗时。

代码

class ThreadPoolTest {
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<Integer> threadNumList = Arrays.asList(4, 8, 16, 25, 34, 50);
        for (Integer threadNum : threadNumList) {
            runTask(threadNum);
        }
    }
 
    public static void runTask(int threadNum) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            threadNum, threadNum, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>(100)
        );
 
        long start = System.currentTimeMillis();
        List<Future<?>> taskList = new ArrayList<>();
        for (int i = 0; i < 55; i++) {
            taskList.add(threadPoolExecutor.submit(() -> {
                doJob();
            }));
        }
        for (Future<?> future : taskList) {
            future.get();
        }
        long time = System.currentTimeMillis() - start;
        System.out.println(threadNum + "个线程,耗时:" + time);
        threadPoolExecutor.shutdown();
    }
 
 
    public static Long doJob() {
        long result = 0L;
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            if (i % 10_000_000 == 0) {
                try {
                    LockSupport.parkNanos(100_000_000);
                } catch (Exception ignore) {
                }
            }
            result += i;
        }
        return result;
    }
}

执行结果

4个线程,耗时:3670028个线程,耗时:19075116个线程,耗时:10835825个线程,耗时:7863234个线程,耗时:5315140个线程,耗时:5356345个线程,耗时:5519650个线程,耗时:55729

期间cpu占用情况如下

总结

1.线程数从4-34期间耗时基本上稳步缩减,但是线程数从34变成50的时候耗时并没有明显减少,反而有增加的趋势,只有cpu利用率一直在飙升。io密集型任务线程池任务的确有一个较优解的,超过这个边界再继续增加线程数,算力会被上下文切换给浪费掉,在执行CPU密集型任务时这个现象会更加明显。

2.即使是50个线程的时候,算力依然有剩余,并没有达到100%利用率。这是因为,单个线程在活跃状态下也并不能完全占用单个核心的所有时间片

3.每次任务执行完都有一个小落差,这个可以自己思考一下为什么。

不同线程执行耗时 以及资源利用率

34个线程,耗时:60389
35个线程,耗时:54077
36个线程,耗时:54886
37个线程,耗时:55035
38个线程,耗时:55231
39个线程,耗时:53961
40个线程,耗时:53701
41个线程,耗时:54406
42个线程,耗时:54794
43个线程,耗时:53585
44个线程,耗时:52690
45个线程,耗时:55242

最后

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • spring cloud gateway网关路由分配代码实例解析

    spring cloud gateway网关路由分配代码实例解析

    这篇文章主要介绍了spring cloud gateway网关路由分配代码实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • SpringCloud hystrix断路器与局部降级全面介绍

    SpringCloud hystrix断路器与局部降级全面介绍

    什么是服务降级?当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作
    2022-10-10
  • JDBC实现数据库增删改查功能

    JDBC实现数据库增删改查功能

    这篇文章主要为大家详细介绍了JDBC实现数据库增删改查功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • 利用JStack排查死锁问题和CPU100%问题

    利用JStack排查死锁问题和CPU100%问题

    无论是再面试过程中还是再实际项目开发当中我们都有可能遇到这两个问题如何利用JStack排查死锁问题和CPU100%问题,文中给出了详细的排查过程和解决方法,需要的朋友可以参考下
    2023-12-12
  • Java中利用BitMap位图实现海量级数据去重

    Java中利用BitMap位图实现海量级数据去重

    有许多方法可以用来去重,比如使用列表、集合等等,但这些方法通常只适用于一般情况,然而,当涉及到大量数据去重时,常见的 Java Set、List,甚至是 Java 8 的新特性 Stream 流等方式就显得不太合适了,本文给大家介绍了Java中利用BitMap位图实现海量级数据去重
    2024-04-04
  • spring-retry简单使用方法

    spring-retry简单使用方法

    这篇文章主要介绍了spring-retry简单使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • SpringBoot根据注解动态执行类中的方法实现

    SpringBoot根据注解动态执行类中的方法实现

    本文主要介绍了SpringBoot根据注解动态执行类中的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • Spring Data JPA系列JpaSpecificationExecutor用法详解

    Spring Data JPA系列JpaSpecificationExecutor用法详解

    这篇文章主要为大家介绍了Spring Data JPA系列JpaSpecificationExecutor用法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 详解SpringBoot中文件上传大小限制问题的解决方案

    详解SpringBoot中文件上传大小限制问题的解决方案

    在开发Web应用程序时,文件上传是一个常见的需求,本文将详细介绍如何分析和解决Spring Boot文件上传大小限制问题,并提供多种配置方式,希望对大家有所帮助
    2025-07-07
  • Spring中@Transactional用法详细介绍

    Spring中@Transactional用法详细介绍

    这篇文章主要介绍了Spring中@Transactional用法详细介绍的相关资料,需要的朋友可以参考下
    2017-02-02

最新评论