惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
H
Help Net Security
F
Fortinet All Blogs
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
量子位
N
Netflix TechBlog - Medium
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
aimingoo的专栏
aimingoo的专栏
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Scott Helme
Scott Helme
The Last Watchdog
The Last Watchdog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
AI
AI
WordPress大学
WordPress大学
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
U
Unit 42
V2EX - 技术
V2EX - 技术
MongoDB | Blog
MongoDB | Blog
Schneier on Security
Schneier on Security
博客园 - Franky
H
Heimdal Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
W
WeLiveSecurity
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
N
News | PayPal Newsroom
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
Hacker News - Newest:
Hacker News - Newest: "LLM"
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网

博客园 - 有容乃大

GitHub相关 2022又要重新找工作开始新的历程 java之面向对象详解(转) java关于for循环的效率优化 JAVA常量池 Java String的intern()注意事项(分JDK1.6及JDK1.7) JAVA的类加载过程 使用RabbitMQ实现延迟任务 JAVA三元运算符空指针引用的坑 Java中static块、构造块、构造函数的执行顺序 Ansj中文分词 关于HashMap、HashSet和ArrayList集合对象容量初始值设置及扩容演示 将异常对象转为字符串 JVM的内存区域划分 深入理解Java String类(综合) 理解JAVA的IO 理解Java注解类型 JAVA中Integer的==和equals注意 JVM原理摘要
JAVA线程池ThreadPoolExecutor的分析和使用(新手踩坑和推荐方案)
有容乃大 · 2021-03-11 · via 博客园 - 有容乃大

一、Java中的ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。
在ThreadPoolExecutor类中提供了四个构造方法:

 1 public class ThreadPoolExecutor extends AbstractExecutorService {
 2     .....
 3     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
 4             BlockingQueue<Runnable> workQueue);
 5  
 6     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
 7             BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 8  
 9     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
10             BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
11  
12     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
13         BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
14     ...
15 }

View Code

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
下面解释下一下构造器中各个参数的含义:
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性。
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
    ArrayBlockingQueue  有界队列。当使用有限的maximumPoolSizes时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度的降低CPU使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞,则系统可能为超过您许可的更多线程安排时间,使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样可会降低吞吐量。
    LinkedBlockingQueue  无界队列。使用无界队列将导致在所有corePoolSize线程都忙时新任务在队列中等待。这样,创建的线程就不会超过corePoolSize(因此,maximumPoolSize的值也就无效了)。
    SynchronousQueue  直接提交。它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界maximumPoolSizes以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增加的可能性。

threadFactory:线程工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略,有以下四种取值:

    ThreadPoolExecutor.AbortPolicy  默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。

    ThreadPoolExecutor.DiscardPolicy  新提交的任务被抛弃但不抛出异常

    ThreadPoolExecutor.DiscardOldestPolicy  丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    ThreadPoolExecutor.CallerRunsPolicy  为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。

二、ThreadPoolExecutor的坑
    1)  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
    一般人会以为如果请求的任务超过3个时,就会自动创建6个线程来执行,其实此线程池最多只会产生3个线程,为什么?
    当前线程数优先与corePoolSize 比较,大于corePoolSize ,则与workQueue容量(capacity)比较,但此处LinkedBlockingQueue默认构造器的capacity值为Integer.MAX_VALUE,你产生的任务数永远不会超过capacity值,那么新任务会一直加入workQueue,而不会创建新的线程,所以你会看到线程池里的线程数永远为3,而不是你期望的6。

三、使用建议

    1) 服务器有超大内存,想节省CPU(线程池核心作务数为3,新任务无限加入workQueue,但是内存会一直暴涨,线程池中线程数永远控制在3个以节省CPU)
        this.threadPoolExecutor = new ThreadPoolExecutor(3, 6, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    2) 服务器CPU资源丰富(线程池核心作务数为10,workQueue缓存的任务控制在20,超过20线程池就会创建新线程最多到100个,但是CPU使用率会暴涨。注:如果线程数量达到100,则新安排的任务根据策略CallerRunsPolicy则会在调用者线程中同步执行)
        this.threadPoolExecutor = new ThreadPoolExecutor(10, 100, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));
        this.threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

    3) 均衡方案--推荐(线程池核心作务数为3,workQueue缓存的任务控制在20,超过20线程池就会创建新线程最多到6个,如果线程数量达到6,则新安排的任务根据策略CallerRunsPolicy则会在调用者线程中同步执行)
        this.threadPoolExecutor = new ThreadPoolExecutor(3, 6, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));
        this.threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

 四、线程对象参数说明

        corePoolSize:初始化时的核心线程数
        maximumPoolSize:初始化时最大的线程数
        activeCount:当前正在活跃的线程数
        largestPoolSize:曾经达到的最大线程数(只有当排队的数量达到BlockingQueue.size才能突破corePoolSize的数量并达到maximumPoolSize)
        completedTaskCount:已经完成的任务数
        queue:线程池中排队的数量
        taskCount:线程池所经历的任务的数量,即 activeCount + queue.size() + completedTaskCount = taskCount

taskCount