首页 > 编程语言 >java线程数量如何确定

java线程数量如何确定

时间:2024-01-25 15:22:54浏览次数:23  
标签:java 任务 private 确定 线程 IO static CPU

1. 概述

  • 使用线程池的好处

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

    虽然使用线程池的好处很多,但是如果其线程数配置得不合理,不仅可能达不到预期效果,反而可能降低成用的性能

2. 线程池分类

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

使用标准构造器ThreadPoolExecutor创建线程池时,会涉及线程数的配置,而线程数的配置异步任务类型是分不开的。

  1. IO密集型任务: 此类任务主要是执行IO操作。由于执行IO操作的时间较长,导致CPU的利用率不高,这类任务CPU常处于空闲状态。Netty的IO读写操作为此类任务的典型例子。
  2. CPU密集型任务: 此类任务主要是执行计算任务。由于响应时间很快,CPU一直在运行,这种任务CPU的利用率很高。
  3. 混合型任务: 此类任务既要执行逻辑计算,又要进行IO操作(如RPC调用、数据库访问)。相对来说,由于执行IO操作的耗时较长(一次网络往返往往在数百毫秒级别),这类任务的CPU利用率也不是太高。Web服务器的HTTP请求处理操作为此类任务的典型例子。

一般情况下,针对以上不同类型的异步任务需要创建不同类型的线程池,并进行针对性的参数配置。

2.2 为 IO密集型任务确定线程数

  • 由于IO密集型任务的CPU使用率较低,导致线程空余时间很多,因此通常需要开CPU核心数两倍的线程。
  • IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。Netty的IO处理任务就是典型的IO密集型任务。所以,Netty的Reactor(反应器)实现类(定制版的线程池)的IO处理线程数默认正好为CPU核数的两倍
//多线程版本Reactor反应器组
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup{
    //IO事件处理线程数
	private static final int DEFAULT_EVENT_LOOP_THREADS;
    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
                SystemPropertyUtil.getInt("io.netty.eventLoopThreads",
                Runtime.getRuntime().availableProcessors() * 2));                   
    }
    protected MultithreadEventLoopGroup(int nThreads,
                                        ThreadFactory threadFactory,
                                        Object...args){
        super(nThreads =0?
              DEFAULT_EVENT_LOOP_THREADS : nThreads,threadFactory,args);
    }
}

IO密集型demo代码

public class ThreadUtil {
    
    /**
     * CPU核数
     **/
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int IO_MAX = Math.max(2, CPU_COUNT * 2);
    private static final int KEEP_ALIVE_SECONDS = 30;
    private static final int QUEUE_SIZE = 128;
    private static class IoIntenseTargetThreadPoolLazyHolder {
        //线程池: 用于IO密集型任务
        private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
            IO_MAX,
            IO_MAX,
            KEEP_ALIVE_SECONDS,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue(QUEUE_SIZE),
            new CustomThreadFactory("io"));

        static {
            EXECUTOR.allowCoreThreadTimeOut(true);
            //JVM关闭时的钩子函数
            Runtime.getRuntime().addShutdownHook(
                new ShutdownHookThread("IO密集型任务线程池", new Callable<Void>() {
                    @Override
                    public Void call() throws Exception {
                        //优雅关闭线程池
                        shutdownThreadPoolGracefully(EXECUTOR);
                        return null;
                    }
                }));
        }
    }

  • IO线程池调用了allowCoreThreadTimeOut(..)方法,并且传入了参数truekeepAliveTime参数所设置的Idle超时策略也将被应用于核心线程,当池中的线程长时间空闲时,可以自行销毁。
  • 使用有界队列缓冲任务而不是无界队列,如果128太小,可以根据具体需要进行增大,但是不能使用无界队列
  • corePoolSizemaximumPoolSize不一致时,当corePoolSize满了而maximumPoolSize没满时即使可以创建线程,但是此时线程池默认不会创建线程,而是将任务加入阻塞队列,等待核心线程空闲,而如果核心线程不空闲,那么任务得不到执行。
  • 如果corePoolSize和maximumPoolSize保持一致,使得在接收到新任务时,如果没有空闲工作线程,就优先创建新的线程去执行新任务,而不是将任务优先加入阻塞队列且等待现有工作线程空闲后再执行。
  • 使用懒汉式单例模式创建线程池,如果代码没有用到此线程池,就不会立即创建。
  • 使用JVM关闭时的钩子函数优雅地自动关闭线程池。

2.3 为 CPU 密集型任务确定线程数

  • CPU密集型任务也叫计算密集型任务,其特点是要进行大量计算而需要消耗CPU资源,比如计算圆周率、对视频进行高清解码等。
  • CPU密集型任务虽然也可以并行完成,但是并行的任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以要最高效地利用CPU,CPU密集型任务并行执行的数量应当等于CPU的核心数。比如说4个核心的CPU,通过4个线程并行执行4个CPU密集型任务,此时的效率是最高的。但是如果线程数远远超出CPU核心数量,就需要频繁地切换线程,线程上下文切换时需要消耗时间,反而会使得任务效率下降。因此,对于CPU密集型的任务来说,线程数等于CPU数就行。
public class ThreadUtil {
    
    /**
     * CPU核数
     **/
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int KEEP_ALIVE_SECONDS = 30;
    private static final int QUEUE_SIZE = 128;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT;

    private static class CpuIntenseTargetThreadPoolLazyHolder {
            //线程池: 用于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"));

            static {
                EXECUTOR.allowCoreThreadTimeOut(true);
                //JVM关闭时的钩子函数
                Runtime.getRuntime().addShutdownHook(
                        new ShutdownHookThread("CPU密集型任务线程池", new Callable<Void>() {
                            @Override
                            public Void call() throws Exception {
                                //优雅关闭线程池
                                shutdownThreadPoolGracefully(EXECUTOR);
                                return null;
                            }
                        }));
            }
        }
}

2.4 为混合型任务确定线程数

  • 混合型任务既要执行逻辑计算,又要进行大量非CPU耗时操作(如RPC调用、数据库访问、网络通信等),所以混合型任务CPU利用率不是太高,非CPU类型耗时往往是CPU耗时的数倍。比如在Web应用处理HTTP请求处理时,一次请求处理会包括DB操作、RPC操作、缓存操作等多种耗时操作。一般来说,一次Web请求的CPU计算耗时往往较少,大致在100500毫秒,而其他耗时操作会占用5001000毫秒,甚至更多的时间。

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

    最佳线程数 = (( 线程等待时间 + 线程CPU时间 ) / 线程CPU时间 ) * CPU核数
    
    经过简单的换算,以上公式可进一步转换为:
    
    最佳线程数目 = ( 线程等待时间 与 线程CPU时间 之比 + 1) * CPU核数
    

    通过公式可以看出:

    1. 等待时间所占比例越高,需要的线程就越多
    2. CPU耗时所占比例越高,需要的线程就越少。

    例子:

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

      (900ms + 100ms) / 100ms * 8 = 10 * 8 = 80

      经过计算,以上案例中需要的线程数为80。

    线程数越高越好吗?使用很多线程是否就一定比单线程高效呢?答案是否定的,比如大名鼎鼎的Redis就是单线程的,但它却非常高效,基本操作都能达到十万量级/秒。

  • 创建混合型线程池时,建议按照前面的最佳线程数估算公式提前预估好线程数(如80),然后设置在环境变量mixed.thread.amount中,测试用例如下:

public class ThreadUtil {
    private static final int MIXED_CORE = 0;  //混合线程池核心线程数
    private static final int MIXED_MAX = 128;  //最大线程数
    public static final String MIXED_THREAD_AMOUNT = "mixed.thread.amount";
    
    //懒汉式单例创建线程池:用于混合型任务
    private static class MixedTargetThreadPoolLazyHolder {
        //首先从环境变量 mixed.thread.amount 中获取预先配置的线程数
        //如果没有对 mixed.thread.amount 做配置,则使用常量 MIXED_MAX 作为线程数
        private static final int max = (null != System.getProperty(MIXED_THREAD_AMOUNT)) ?
                Integer.parseInt(System.getProperty(MIXED_THREAD_AMOUNT)) : MIXED_MAX;
        //线程池: 用于混合型任务
        private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
                max,
                max,
                KEEP_ALIVE_SECONDS,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue(QUEUE_SIZE),
                new CustomThreadFactory("mixed"));
        
        static {
            EXECUTOR.allowCoreThreadTimeOut(true);
            //JVM关闭时的钩子函数
            Runtime.getRuntime().addShutdownHook(new ShutdownHookThread("混合型任务线程池", new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                    //优雅关闭线程池
                    shutdownThreadPoolGracefully(EXECUTOR);
                    return null;
                }
            }));
        }
    }
}

标签:java,任务,private,确定,线程,IO,static,CPU
From: https://www.cnblogs.com/ccblblog/p/17987237

相关文章

  • iOS 多线程复习
    iOS中的线程主要有四种:1.pThread2.NSThread3.GCD4.NSOpreaction基础知识:线程、任务和队列的概念: 异步、同步&并行、串行的特点:组合特点:  1.pThread C语言所写,面向过程,使用较少.oc:#pragmaMark-pThread-(void)pThreadDemo{pthread_tpthre......
  • java线程池-2
    1.Executors创建线程池的潜在问题在很多公司的编程规范中,非常明确地禁止使用Executors创建线程池。为什么呢?这里从源码讲起,介绍使用Executors工厂方法创建线程池将会面临的潜在问题。1.1Executors创建固定数量的线程池的潜在问题使用newFixedThreadPool工厂方法固定数......
  • 2024年1月Java项目开发指南4:IDEA里配置MYSQL
    提前声明:文章首发博客园(cnblogs.com/mllt)自动“搬家”(同步)到CSDN,如果博客园中文章发生修改是不会同步过去的,所以建议大家到我的博客园中查看前提条件:1.你已经设计好了数据库,并成功创建了数据库。2.你的springboot项目中已经配置好了MySQL的连接。填写好信息后点测试连......
  • JavaScript 中 eval() 函数
    JavaScript的eval()函数的作用是将一个字符串作为脚本代码进行解析和执行。它可以动态地执行字符串中的JavaScript代码,并返回执行结果。eval()函数可以用于执行任何有效的JavaScript代码,包括声明变量、定义函数、执行表达式等。eval()函数的语法如下:varformArray=$('#formRec......
  • Java 中 float 与 double 的区别
    1.float是单精度浮点数,内存分配4个字节,占32位,有效小数位6-7位double是双精度浮点数,内存分配8个字节,占64位,有效小数位15位 2.java中默认声明的小数是double类型的,如doubled=4.0如果声明:floatx=4.0则会报错,需要如下写法:floatx=4.0f或者floatx=(float)4.0其中4.0f后......
  • GDB调试之多线程死锁调试(二十四)
    调试代码如下所示:#include<thread>#include<iostream>#include<vector>#include<mutex>usingnamespacestd;mutex_mutex1;mutex_mutex2;intdata1;intdata2;intdo_work_1(){ std::cout<<"线程函数do_work_1开始"<<......
  • HTTP连接池在Java中的应用:轻松应对网络拥堵
    网络拥堵是现代生活中无法避免的问题,尤其是在我们这个“点点点”时代,网页加载速度直接影响到我们的心情。此时,我们需要一位“救世主”——HTTP连接池。今天,就让我们一起探讨一下,这位“救世主”如何在Java中大显神通。首先,我们要明白,什么是HTTP连接池?简单来说,它就像一个“连接银行”......
  • 使用Java中的OkHttp库进行HTTP通信:快速、简单且高效
    在Java的世界里,进行HTTP通信的方式多种多样。其中,OkHttp以其简单、高效和强大的功能受到了开发者的广泛欢迎。今天,我们就来深入探讨如何使用OkHttp库在Java中进行HTTP通信。首先,OkHttp是一个基于HTTP/2和SPDY的客户端,提供了现代且高效的通信方式。它不仅支持同步请求和异步请求,还提......
  • rust使用lazy_static对全局变量多线程并发读写示例
    首先需要在项目依赖Cargo.toml添加lazy_static依赖项[dependencies]lazy_static="1.4.0"示例代码如下:uselazy_static::lazy_static;usestd::sync::{RwLock,RwLockReadGuard,RwLockWriteGuard};usestd::thread;#[derive(Debug)]structSharedData{data:Vec<......
  • jmeter读取csv文件控制多线程不重复读取
    在Jmeter中设置并发为S,循环次数为N时,参数化文件可能被重复读取N次,无法保证每次读取的数据均不一样,此处介绍保证数据不重复的方法。在线程组下添加一个CSVDataSetConfig,具体配置如下图:将配置中默认:RecycleonEOF=True,StopthreadonEOF=False修改为:RecycleonEO......