ThreadPoolExecutor线程池参数及其设置规则

简介: ThreadPoolExecutor线程池参数及其设置规则

1)为什么需要线程池?


重复利用线程资源,减少创建线程和销毁线程系统所花费的开销,也可以限制请求过多带来的系统压力。在一些场景也可以增加处理的速度。


2)线程池内部怎么保证线程安全?


也就是说一个任务怎么保证不被两个线程都执行?

线程池内部有两部分组成一部分是task任务列表 一部分是线程数组,在处理任务是都要去上锁,这个锁其实就是一个变量。等这个任务拿到后再释放锁。


3)创建线程的方式


阿里公司明确指出不建议使用Executors静态方法创建线程,

比如FixedThreadPool 和 SingleThreadPool 队列长度是integer的最大值,如果控制不好的话容易出现内存溢出。

比如CachedThreadPool 和 ScheduledThreadPool 是创建的线程数量是integer的最大值也有可能导致内存溢出。


通常都会用 ThreadPoolExecutor 创建线程池。好处是线程可以控制。


CachedThreadPool底层

·因为没有核心线程,所以任务直接加到SynchronousQueue队列。

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<Runnable>(),
    threadFactory);
}

newSingleThreadExecutor底层

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
      0L, TimeUnit.MILLISECONDS,
      new LinkedBlockingQueue<Runnable>(),
    threadFactory));
}


4) ThreadPoolExecutor参数使用

corePoolSize 核心线程数会一直存在,除非allowCoreThreadTimeOut设置为true<br />    maximumPoolSize 线程池最大线程数<br />    keepAliveTime:除了核心线程数外的线程 如果没有任务多久释放。<br />    unit:超时时间的单位<br />    workQueue:工作队列,保存未执行的Runnable 任务<br />    threadFactory:创建线程的工厂类<br />    handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。


5) 线程池的工作机制

如果没有空闲的线程执行该任务,并且线程数没有达到核心线程数,会创建一个新的线程。(如果创建的线程存在空闲的就直接用了)。

如果没有空闲的线程执行该任务,当前线程数已经达到核心线程数,会把任务放到任务队里中。

如果没有空闲的线程执行该任务,当前线程数已经达到核心线程数,如果队列满了会创建一个新的线程。

如果没有空闲的线程执行该任务,当前线程数已经到达最大线程数,会通过handler执行拒绝策略。


6)拒绝策略分四种

1.默认直接拒绝抛出ThreadPoolExecutor.AbortPolicy RejectedExecutionException

2.直接不处理ThreadPoolExecutor.DiscardPolicy()

3.把加入队列最早的任务删除。ThreadPoolExecutor.DiscardOldestPolicy()

4.让调用线程池的任务去处理。ThreadPoolExecutor.CallerRunsPolicy()


自定义拒绝策略 实现RejectedExecutionHandler接口,实现抽象方法rejectedExecution方法。

当引用自定义拒绝策略时会初始化自定义拒绝策略类的构造方法。

当线程堵塞触发拒绝策略时会执行rejectedExecution方法。


这几种拒绝策略都是静态内部类实现RejectedExecutionHandler接口。


也可以通过第三方dubbo拒绝策略AbortPolicyWithReport

来处理可以继承AbortPolicy类,重写rejectedExecution

打印出dubbo日志抛出异常


7)队列有哪些?

有五种比较常用的是ArrayBlockingQueue和LinkedBlockingQueue和SynchronousQueue。

ArrayBlockingQueue 是有边界的堵塞队列。

LinkedBlockingQueue 是无边界的堵塞队列,也可以设置边界值。比ArrayBlockingQueue吞吐量要高,原因是ArrayBlockingQueue添加任务和移除任务用的是同一把锁,而LinkedBlockingQueue分别会有一把锁。

SynchronousQueue是是无界的,队列的size始终为0,每个添加任务操作需要等待任务移除操作,反之也是一样。有时我们希望绕开队列,直接分配接收者线程,此时可采用SynchronousQueue,只要当前池的大小还小于最大值,ThreadPoolExecutor就会创建新线程。

SynchronousQueue并不是真正的队列,而是一种管理直接在线程之间移交信息的机制,所有CachedThreadPool用的就是此队列,最大线程数无限大。


8) 如何为线程池设置合适的线程参数?

目前根据一些开源框架,设置多少个线程数量通常是根据应用的类型 :I/O 密集型、CPU 密集型


  • I/O密集型


I/O密集型的场景在开发中比较常见,比如像 MySQL数据库读写、文件的读写、网络通信等任务,这类任务不会 特别消耗CPU资源,但是IO操作比较耗时,会占用比较多时间;

IO密集型通常设置为 2n+1,其中 n 为 CPU 核数;

说白了,对于i/o密集型的场景,不太占用cpu资源,所以并发的任务数大于cpu的核数,这样的话能更加充分的利用CPU资源;


  • CPU密集型


CPU密集型的场景,比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,这些场景大部分都是纯 CPU计算;

CPU密集型通常设置为n+1,这样也可避免多线程环境下CPU资源挣钱带来上下文频繁切换的开销;

如何获取当前服务器的cpu核数?
int cors= Runtime.getRuntime().availableProcessors();
无界队列问题

实际运行中,我们一般会设置线程池的阻塞队列长度,如果不设置,则采用默认值:

private int corePoolSize = 1;
private int maxPoolSize = Integer.MAX_VALUE;
private int keepAliveSeconds = 60;
private int queueCapacity = Integer.MAX_VALUE;

在这个过程中,如果设置或者使用不当,容易造成内存溢出问题,同时如果设置了无界队列,那么线程池的最大线程数也就失去了意义;

所以企业开发中会命令禁止使用默认的队列长度

相关文章
|
4月前
|
SQL druid Java
线程池相关故障问题之Druid数据库连接池中,为何需要设置TransactionTimeout
线程池相关故障问题之Druid数据库连接池中,为何需要设置TransactionTimeout
128 0
|
4月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
45 0
|
2月前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
237 10
spring多线程实现+合理设置最大线程数和核心线程数
|
1月前
|
Java
线程池设置原则
线程池设置原则
|
1月前
|
设计模式 Java 物联网
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
58 0
|
3月前
|
缓存 Java
异步&线程池 线程池的七大参数 初始化线程的4种方式 【上篇】
这篇文章详细介绍了Java中线程的四种初始化方式,包括继承Thread类、实现Runnable接口、实现Callable接口与FutureTask结合使用,以及使用线程池。同时,还深入探讨了线程池的七大参数及其作用,解释了线程池的运行流程,并列举了四种常见的线程池类型。最后,阐述了在开发中使用线程池的原因,如降低资源消耗、提高响应速度和增强线程的可管理性。
异步&线程池 线程池的七大参数 初始化线程的4种方式 【上篇】
|
3月前
|
消息中间件 Java 大数据
"深入理解Kafka单线程Consumer:核心参数配置、Java实现与实战指南"
【8月更文挑战第10天】在大数据领域,Apache Kafka以高吞吐和可扩展性成为主流数据流处理平台。Kafka的单线程Consumer因其实现简单且易于管理而在多种场景中受到欢迎。本文解析单线程Consumer的工作机制,强调其在错误处理和状态管理方面的优势,并通过详细参数说明及示例代码展示如何有效地使用KafkaConsumer类。了解这些内容将帮助开发者优化实时数据处理系统的性能与可靠性。
88 7
|
3月前
|
监控 Java
ThreadPoolExecutor 线程执行超时,释放线程
ThreadPoolExecutor 线程执行超时,释放线程
156 1
|
4月前
|
监控 Java
多线程线程池问题之创建线程如何解决
多线程线程池问题之创建线程如何解决
|
4月前
|
Java
Java演进问题之单个虚拟机的最大线程数量一般会设置到200至400条如何解决
Java演进问题之单个虚拟机的最大线程数量一般会设置到200至400条如何解决