Java 线程池的使用

Java线程池的使用

 

 

 

1、 线程池是什么?

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服 务器中,如 MySQL。 线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等, 同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发 执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方 面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。之前我们在使用多线程都是用 Thread 的 start() 来创建启动一个线程,但是在实际开发中,如果每个请求到达就创建一个新线程,开销是相当大的。服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。这就引入了线程池概念。


优点:
  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造 成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资 源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用 线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多 的功能。比如延时定时线程池 ScheduledThreadPoolExecutor,就允许任 务延期执行或定期执行。

2、Executors 线程池工具提供四种线程池

ExecutorService ex = Executors.newCachedThreadPool();
ExecutorService ex = Executors.newFixedThreadPool(poolSize)

 

3、使用线程池的步骤

  • 1、创建线程池

  • 2、创建任务

  • 3、执行任务

  • 4、关闭线程池

 

4、Java 中的线程池核心实现类是 ThreadPoolExecutor

          在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

 ThreadPoolExecutor 的继承关系:

 ThreadPoolExecutor 实 现 的 顶 层 接 口 是 Executor, 顶 层 接 口 Executor 提 供 了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如 何调度线程来执行任务,用户只需提供 Runnable 对象,将任务的运行逻辑提交 到执行器 (Executor) 中,由 Executor 框架完成线程的调配和任务的执行部分。 ExecutorService 接口增加了一些能力:(1)扩充执行任务的能力,补充可以为一 个或一批异步任务生成 Future 的方法;(2)提供了管控线程池的方法,比如停止线 程池的运行。AbstractExecutorService 则是上层的抽象类,将执行任务的流程 串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类 ThreadPoolExecutor 实现最复杂的运行部分,ThreadPoolExecutor 将会一方面 维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并 行任务。

ThreadPoolExecutor 是如何运行,如何同时维护线程和执行任务的呢?其运行机制 如下图所示:

 

 线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不 直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管 理、线程管理。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任 务后续的流转:(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3) 拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进 行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取 不到任务的时候,线程就会被回收。

ThreadPoolExecutor 的运行状态有 5 种,分别为:

 

任务执行机制

任务调度

任务调度是线程池的主要入口,当用户提交了一个任务,接下来这个任务将如何执行 都是由这个阶段决定的。了解这部分就相当于了解了线程池的核心运行机制。 首先,所有任务的调度都是由 execute 方法完成的,这部分完成的工作是:检查现在 线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线 程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:

1. 首先检测线程池运行状态,如果不是 RUNNING,则直接拒绝,线程池要保 证在 RUNNING 的状态下执行任务。

2. 如果 workerCount < corePoolSize,则创建并启动一个线程来执行新提 交的任务。

3. 如果 workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任 务添加到该阻塞队列中。

4. 如 果 workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提 交的任务。

5. 如果 workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满 , 则根据拒绝策略来处理该任务 , 默认的处理方式是直接抛异常。

其执行流程如下图所示:

 

 任务缓冲

       任务缓冲模块是线程池能够管理任务的核心部分。线程池的本质是对任务和线程的管 理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才 可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现 的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。 阻塞队列 (BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是: 在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程 会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元 素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。下图中展示了线程 1 往阻塞队列中添加元素,而线程 2 从阻塞队列中移除元素:

 使用不同的队列可以实现不一样的任务存取策略。在这里,我们可以再介绍下阻塞队 列的成员:

 

 任务拒绝

任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存 队列已满,并且线程池中的线程数目达到 maximumPoolSize 时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。 拒绝策略是一个接口,其设计如下:

public interface RejectedExecutionHandler {
 void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

用户可以通过实现这个接口去定制拒绝策略,也可以选择 JDK 提供的四种已有拒绝 策略,其特点如下:

 

ThreadPoolExcutor构造函数的定义:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) ;
  • corePoolSize
    线程池大小,决定着新提交的任务是新开线程去执行还是放到任务队列中,也是线程池的最最核心的参数。一般线程池开始时是没有线程的,只有当任务来了并且线程数量小于corePoolSize才会创建线程。

          1.核心线程会一直存活,及时没有任务需要执行
          2.当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
          3.设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。

  • maximumPoolSize
    线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变运行的最大线程的数目。

         1.当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
         2.当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

  • keepAliveTime
    在线程数量超过corePoolSize后,多余空闲线程的最大存活时间。

          1.当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
          2.如果allowCoreThreadTimeout=true,则会直到线程数量=0

  • unit
    keepAliveTime的时间单位
  • workQueue
    存放来不及处理的任务的队列,是一个BlockingQueue。
  • threadFactory
    生产线程的工厂类,可以定义线程名,优先级等。
  •   handler     拒绝策略,当任务来不及处理的时候,如何处理。

 两种情况会拒绝处理任务:

1.当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
2.当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。

线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置,默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor类有几个内部实现类来处理拒绝任务:

  • 1.AbortPolicy 丢弃任务,抛运行时异常
  • 2.CallerRunsPolicy 执行任务
  • 3.DiscardPolicy 忽视,什么都不会发生
  • 4.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
  • 5.实现RejectedExecutionHandler接口,可自定义处理器

二:ThreadPoolExecutor执行顺序:

  • 1.当线程数小于核心线程数时,创建线程。
  • 2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  • 3.当线程数大于等于核心线程数,且任务队列已满
  • 3.1若线程数小于最大线程数,创建线程
  • 3.2若线程数等于最大线程数,抛出异常,拒绝任务

 

三:线程池参数的合理设置
为了说明合理设置的条件,我们首先确定有以下几个相关参数:
1.tasks,程序每秒需要处理的最大任务数量(假设系统每秒任务数为100~1000)
2.tasktime,单线程处理一个任务所需要的时间(每个任务耗时0.1秒)
3.responsetime,系统允许任务最大的响应时间(每个任务的响应时间不得超过2秒)

corePoolSize
每个任务需要tasktime秒处理,则每个线程每秒可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime)。
即tasks*tasktime个线程数。假设系统每秒任务数为100到1000之间,每个任务耗时0.1秒,则需要100x0.1至1000x0.1,即10到100个线程。那么corePoolSize应该设置为大于10。
具体数字最好根据80、20原则,即80%情况下系统每秒任务数,若系统80%的情况下任务数小于200,最多时为1000,则corePoolSize可设置为20。

queueCapacity:任务队列的长度
任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)responsetime: (20/0.1)2=400,即队列长度可设置为400。
如果队列长度设置过大,会导致任务响应时间过长,如以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

maxPoolSize:最大线程数
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。

keepAliveTime:
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。keepAliveTiime设定值可根据任务峰值持续时间来设定。

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。

 
有界的任务队列有界的任务队列可以使用ArrayBlockingQueue实现
pool = new ThreadPoolExecutor(1, 
2, 
1000, 
TimeUnit.MILLISECONDS, 
new ArrayBlockingQueue<Runnable>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

 

无界的任务队列有界任务队列可以使用LinkedBlockingQueue实现,如下所示

pool = new ThreadPoolExecutor(1, 
2, 
1000, 
TimeUnit.MILLISECONDS, 
new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);

 自定义拒绝策略

  pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5),
                Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println(r.toString()+"执行了拒绝策略");
                
            }
        });

 

5、全局线程池 

public class GlobalThreadPool {

    /**
     * 核心线程大小
     * 程序每秒需要处理的最大任务数量(10) * 单线程处理一个任务所需要的时间(1s)
     */
    private static final Integer CORE_POOL_SIZE = 10;
    /**
     * 最大线程大小
     */
    private static final Integer MAXIMUM_POOL_SIZE = 20;
    /**
     * 检测空闲线程的时间周期
     */
    private static final Long KEEP_ALIVE_TIME = 20L;
    /**
     * 阻塞队列大小
     * (corePoolSize/taskTime) * responseTime
     */
    private static final Integer BLOCK_QUEUE_SIZE = 2000;

    private static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(BLOCK_QUEUE_SIZE));
   
    public static Future submitTask(Callable task){
        return tpe.submit(task);
    }

    public static ThreadPoolExecutor getExecutor() {
        return tpe;
    }
}

 

6、创建任务:

任务分为两种:一种是有返回值的( callable ),一种是没有返回值的( runnable ). Callable与 Future 两功能是Java在后续版本中为了适应多并法才加入的,Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。

  • 无返回值的任务就是一个实现了runnable接口的类.使用run方法.
  • 有返回值的任务是一个实现了callable接口的类.使用call方法.

Callable和Runnable的区别如下:

  • Callable定义的方法是call,而Runnable定义的方法是run。
  • Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
  • Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

7、执行任务

      通过java.util.concurrent.ExecutorService接口对象来执行任务,该对象有两个方法可以执行任务execute和submit。execute这种方式提交没有返回值,也就不能判断是否执行成功。submit这种方式它会返回一个Future对象,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成。

execute与submit区别:

  • 接收的参数不一样
  • submit有返回值,而execute没有
  • submit方便Exception处理
  • execute是Executor接口中唯一定义的方法;submit是ExecutorService(该接口继承Executor)中定义的方法
8、使用线程池
(1)调用Runnable
pool.execute(new Runnable() {
    @Override
    public void run() {
        // 输出内容:MyThreadFactory_testThread_0
        System.out.println(Thread.currentThread().getName());
    }
});
 
(2)调用callable
Future<String> future = pool.submit(new Callable<String>() {
     @Override
     public String call() throws Exception {
         return Thread.currentThread().getName();
     }
 });

 

9、Spring 提供的线程池:ThreadPoolTaskExecutor

/**
 * @Author dw
 * @ClassName GlobalThreadPool
 * @Description 全局线程池配置
 * @Date 2022/5/26 15:08
 * @Version 1.0
 */
@Configuration
public class GlobalThreadPool {

    private static final Logger logger = LoggerFactory.getLogger(GlobalThreadPool.class);

    private static final Integer CORE_POOL_SIZE = 5;
    private static final Integer QUEUE_SIZE = 10;

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolExecutor = new ThreadPoolTaskExecutor();
        // 1、cpu密集型 cpu+1; 2、IO密集型 cpu*2 + 1
        int maxPoolSize = Runtime.getRuntime().availableProcessors() * 2;
        logger.info("ThreadPoolTaskExecutor-CORE_POOL_SIZE: {}", CORE_POOL_SIZE);
        logger.info("ThreadPoolTaskExecutor-MAX_POOL_SIZE: {}", maxPoolSize);
        threadPoolExecutor.setCorePoolSize(CORE_POOL_SIZE);
        threadPoolExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolExecutor.setQueueCapacity(QUEUE_SIZE);
        threadPoolExecutor.setKeepAliveSeconds(60);
        threadPoolExecutor.setThreadNamePrefix("executor-custom-");
        threadPoolExecutor.setAllowCoreThreadTimeOut(false);
        threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return threadPoolExecutor;
    }
}

 

10、线程池在业务中的实践

 业务背景

在当今的互联网业界,为了最大程度利用 CPU 的多核性能,并行运算的能力是不可 或缺的。通过线程池管理线程获取并发性是一个非常基础的操作,让我们来看两个典 型的使用线程池获取并发性的场景。

场景 1:快速响应用户请求 描述:用户发起的实时请求,服务追求响应时间。比如说用户要查看一个商品的信 息,那么我们需要将商品维度的一系列信息如商品的价格、优惠、库存、图片等等聚 合起来,展示给用户。 分析:从用户体验角度看,这个结果响应的越快越好,如果一个页面半天都刷不出, 用户可能就放弃查看这个商品了。而面向用户的功能聚合通常非常复杂,伴随着调用 与调用之间的级联、多级级联等情况,业务开发同学往往会选择使用线程池这种简单 的方式,将调用封装成任务并行的执行,缩短总体响应时间。另外,使用线程池也是 有考量的,这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置 队列去缓冲并发任务,调高 corePoolSizemaxPoolSize 去尽可能创造多的线程 快速执行任务。

 场景 2:快速处理批量任务

描述:离线的大量计算任务,需要快速执行。比如说,统计某个报表,需要计算出全 国各个门店中有哪些商品有某种属性,用于后续营销策略的分析,那么我们需要查询 全国所有门店中的所有商品,并且记录具有某属性的商品,然后快速生成报表。 分析:这种场景需要执行大量的任务,我们也会希望任务执行的越快越好。这种情况 下,也应该使用多线程策略,并行计算。但与响应速度优先的场景区别在于,这类 场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在 单位时间内处理更多的任务,也就是吞吐量优先的问题。所以应该设置队列去缓冲 并发任务,调整合适的 corePoolSize 去设置处理任务的线程数。在这里,设置的线 程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低 吞吐量。

 

11、CPU和线程概述、线程池如何合理设置

物理核

  • 物理核数量=cpu数(机子上装的cpu的数量)*每个cpu的核心数

虚拟核

  • 所谓的4核8线程,4核指的是物理核心。通过超线程技术,用一个物理核模拟两个虚拟核,每个核两个线程,总数为8线程。
  • 在操作系统看来是8个核,但是实际上是4个物理核。
  • 通过超线程技术可以实现单个物理核实现线程级别的并行计算,但是比不上性能两个物理核。

单核cpu和多核cpu

  • 都是一个cpu,不同的是每个cpu上的核心数
  • 多核cpu是多个单核cpu的替代方案,多核cpu减小了体积,同时也减少了功耗
  • 一个核心只能同时执行一个线程

进程和线程

  • 进程是操作系统进行资源(包括cpu、内存、磁盘IO等)分配的最小单位
  • 线程是cpu调度和分配的基本单位
  • 资源分配给进程,线程共享进程资源。

对比 进程 线程

  • 定义 进程是程序运行的一个实体的运行过程,是系统进行资源分配和调配的一个独立单位 线程是进程运行和执行的最小调度单位
  • 系统开销 创建撤销切换开销大,资源要重新分配和收回 仅保存少量寄存器的内容,开销小,在进程的地址空间执行代码
  • 拥有资产 资源拥有的基本单位 基本上不占资源,仅有不可少的资源(程序计数器,一组寄存器和栈)
  • 调度 资源分配的基本单位 独立调度分配的单位
  • 安全性 进程间相互独立,互不影响 线程共享一个进程下面的资源,可以互相通信和影响
  • 地址空间 系统赋予的独立的内存地址空间 由相关堆栈寄存器和和线程控制表TCB组成,寄存器可被用来存储线程内的局部变量

线程切换

  • cpu给线程分配时间片(也就是分配给线程的时间),执行完时间片后会切换都另一个线程。
  • 切换之前会保存线程的状态,下次时间片再给这个线程时才能知道当前状态。
  • 从保存线程A的状态再到切换到线程B时,重新加载线程B的状态的这个过程就叫上下文切换。
  • 上下切换时会消耗大量的cpu时间。

线程开销

  • 上下文切换消耗
  • 线程创建和消亡的开销
  • 线程需要保存维持线程本地栈,会消耗内存

串行、并发、并行
串行

  • 多个任务,执行时一个执行完再执行另一个。
  • 比喻:吃完饭再看球赛。

并发

  • 多个线程在单个核心运行,同一时间一个线程运行,系统不停切换线程,看起来像同时运行,实际上是线程不停切换。
  • 比喻: 一会跑去食厅吃饭,一会跑去客厅看球赛。

并行

  • 每个线程分配给独立的核心,线程同时运行。
  • 比喻:一边吃饭一边看球赛。

多核下线程数量选择
计算密集型

  • 程序主要为复杂的逻辑判断和复杂的运算。
  • cpu的利用率高,不用开太多的线程,开太多线程反而会因为线程切换时切换上下文而浪费资源。

IO密集型

  • 程序主要为IO操作,比如磁盘IO(读取文件)和网络IO(网络请求)。
  • 因为IO操作会阻塞线程,cpu利用率不高,可以开多点线程,阻塞时可以切换到其他就绪线程,提高cpu利用率。

提高性能的方向

  • 提高性能的一种方式:提高硬件水平,处理速度或核心数。
  • 另一种方式:根据场景,合理设置线程数,软件上提高cpu利用率。(线程数的设置,如果是CPU密集型应用,则线程池大小设置为N+1,如果是IO密集型应用,则线程池大小设置为2N+1)
  • 线程数的设置也可参考公式线程数=CPU核心数/(1-阻塞系数),看你的任务是计算密集性的还是IO密集性的,IO密集型的,阻塞系数加大,计算密集型的阻塞系数减少,阻塞系数可以将其理解为>阻塞时间/计算时间;

并发编程网上关于线程池的总结
高并发、任务执行时间短的业务怎样使用线程池?

并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

  1. 高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
  2. 并发不高、任务执行时间长的业务要区分开看:
  3. 假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务
  4. 假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换
  5. 并发高、业务执行时间长,这种类型任务需要分段定位优化,第一看看这些业务里面某些数据是否能做缓存,其二增加服务器提高硬件性能,其三是参考设置合理的线程池,最后,业务执行时间长的问题,需要考虑进行业务的拆分和解耦。

 

 

 

 

 

posted @ 2020-03-23 12:02  邓维-java  阅读(700)  评论(0编辑  收藏  举报