引言

JAVA 语言为我们提供了两种基础线程池的选择:

ThreadPoolExecutor

ScheduledThreadPoolExecutor

它们都实现了 ExecutorService 接口

注意,ExecutorService接口本身和“线程池”并没有直接关系,它的定义更接近“执行器”,而“使用线程管理的方式进行实现”只是其中的一种实现方式。

这里重点来看 ThreadPoolExecutor 类的使用,至于 ScheduledThreadPoolExecutor 类无非就是在 ThreadPoolExecutor 类基础上增加了定时调度功能。

ThreadPoolExecutor

构造器

ThreadPoolExecutor提供了四个构造方法:

我们以最后一个构造方法(参数最多的那个),对其参数进行解释:

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

知道了各个参数的作用后,我们开始构造符合我们期待的线程池。

自定义线程池

public class ThreadTest {

public static void main(String[] args) throws InterruptedException, IOException {

int corePoolSize = 2;

int maximumPoolSize = 4;

long keepAliveTime = 10;

TimeUnit unit = TimeUnit.SECONDS;

BlockingQueue workQueue = new ArrayBlockingQueue<>(2);

ThreadFactory threadFactory = new NameTreadFactory();

RejectedExecutionHandler handler = new MyIgnorePolicy();

ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,

workQueue, threadFactory, handler);

executor.prestartAllCoreThreads(); // 预启动所有核心线程

for (int i = 1; i <= 10; i++) {

MyTask task = new MyTask(String.valueOf(i));

executor.execute(task);

}

System.in.read(); //阻塞主线程

}

static class NameTreadFactory implements ThreadFactory {

private final AtomicInteger mThreadNum = new AtomicInteger(1);

@Override

public Thread newThread(Runnable r) {

Thread t = new Thread(r, "my-thread-" + mThreadNum.getAndIncrement());

System.out.println(t.getName() + " has been created");

return t;

}

}

public static class MyIgnorePolicy implements RejectedExecutionHandler {

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

doLog(r, e);

}

private void doLog(Runnable r, ThreadPoolExecutor e) {

// 可做日志记录等

System.err.println( r.toString() + " rejected");

// System.out.println("completedTaskCount: " + e.getCompletedTaskCount());

}

}

static class MyTask implements Runnable {

private String name;

public MyTask(String name) {

this.name = name;

}

@Override

public void run() {

try {

System.out.println(this.toString() + " is running!");

Thread.sleep(3000); //让任务执行慢点

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public String getName() {

return name;

}

@Override

public String toString() {

return "MyTask [name=" + name + "]";

}

}

}

在上面的代码中,我们创建线程池的时候使用了ThreadPoolExecutor 中最复杂的一个构造器

ThreadPoolExecutor 逻辑结构

一定要注意一个概念,即存在于线程池容器中的一定是 Thread 线程对象,而不是您要求运行的任务(所以叫线程池而不叫任务池也不叫对象池,更不叫游泳池);您要求运行的任务将被线程池分配给某一个空闲的 Thread 线程对象运行。

从上图中,我们可以看到构成线程池的几个重要元素:

等待队列:顾名思义,就是您调用线程池对象的submit()方法或者execute()方法,要求线程池运行的任务(这些任务必须实现Runnable接口或者Callable接口)。但是出于某些原因线程池并没有马上运行这些任务,而是送入一个队列等待执行(这些原因后文马上讲解)。

核心线程:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是您创建线程时所设置的corePoolSize参数决定的。如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段)。

非核心线程:一旦任务数量过多(由等待队列的特性决定),线程池将创建“非核心线程”临时帮助运行任务。您设置的大于corePoolSize参数小于maximumPoolSize参数的部分,就是线程池可以临时创建的“非核心线程”的最大数量。这种情况下如果某个线程没有运行任何任务,在等待keepAliveTime时间后,这个线程将会被销毁,直到线程池的线程数量重新达到corePoolSize。并不是所谓的“非核心线程”才会被回收;而是谁的空闲时间达到keepAliveTime这个阀值,就会被回收。直到线程池中线程数量等于corePoolSize为止。

maximumPoolSize 参数也是当前线程池允许创建的最大线程数量。那么如果您设置的corePoolSize参数和您设置的maximumPoolSize参数一致时,线程池在任何情况下都不会回收空闲线程。keepAliveTime和timeUnit也就失去了意义。

keepAliveTime参数和timeUnit参数也是配合使用的。keepAliveTime参数指明等待时间的量化值,timeUnit指明量化值单位。例如keepAliveTime=1,timeUnit为TimeUnit.MINUTES,代表空闲线程的回收阀值为1分钟。

ThreadPoolExecutor 工作流程

首先您可以通过线程池提供的submit()方法或者execute()方法,要求线程池执行某个任务。线程池收到这个要求执行的任务后,会有几种处理情况:

1.1、如果当前线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新的线程运行您的任务,无论之前已经创建的线程是否处于空闲状态。

1.2、如果当前线程池中运行的线程数量已经达到设置的corePoolSize大小,线程池会把您的这个任务加入到等待队列中。直到某一个的线程空闲了,线程池会根据您设置的等待队列规则,从队列中取出一个新的任务执行。

1.3、如果根据队列规则,这个任务无法加入等待队列。这时线程池就会创建一个“非核心线程”直接运行这个任务。注意,如果这种情况下任务执行成功,那么当前线程池中的线程数量一定大于corePoolSize。

1.4、如果这个任务,无法被“核心线程”直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,且您没有为线程池设置RejectedExecutionHandler。这时线程池会抛出RejectedExecutionException异常,即线程池拒绝接受这个任务。(实际上抛出RejectedExecutionException异常的操作,是ThreadPoolExecutor线程池中一个默认的RejectedExecutionHandler实现:AbortPolicy,这在后文会提到)

一旦线程池中某个线程完成了任务的执行,它就会试图到任务等待队列中拿去下一个等待任务(所有的等待任务都实现了BlockingQueue接口,按照接口字面上的理解,这是一个可阻塞的队列接口),它会调用等待队列的poll()方法,并停留在哪里。

当线程池中的线程超过您设置的corePoolSize参数,说明当前线程池中有所谓的“非核心线程”。那么当某个线程处理完任务后,如果等待keepAliveTime时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,对所谓的“核心线程”和“非核心线程”是一视同仁的,直到线程池中线程的数量等于您设置的corePoolSize参数时,回收过程才会停止。

不常用的设置

在 ThreadPoolExecutor 线程池中,有一些不常用的设置。我建议如果您在应用场景中没有特殊的要求,就不需要使用这些设置

allowCoreThreadTimeOut

前文我们讨论到,线程池回收线程只会发生在当前线程池中线程数量大于 corePoolSize 参数的时候;当线程池中线程数量小于等于 corePoolSize 参数的时候,回收过程就会停止。

allowCoreThreadTimeOut 设置项可以要求线程池:将包括“核心线程”在内的,没有任务分配的任何线程,在等待keepAliveTime时间后全部进行回收:

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(1));

poolExecutor.allowCoreThreadTimeOut(true);

prestartAllCoreThreads

前文我们还讨论到,当线程池中的线程还没有达到您设置的corePoolSize参数值的时候,如果有新的任务到来,线程池将创建新的线程运行这个任务,无论之前已经创建的线程是否处于空闲状态。

prestartAllCoreThreads设置项,可以在线程池创建,但还没有接收到任何任务的情况下,先行创建符合 corePoolSize 参数值的线程数:

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue(1));

poolExecutor.prestartAllCoreThreads();

总结

如果 Java 内置的四种线程池 newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor 仍然无法满足实际场景需要时,则需要通过自定义线程池,让线程池更好的满足我们的需求。

java线程池 core_Java 线程池 ThreadPoolExecutor 的使用相关推荐

  1. 【Java 并发编程】线程池机制 ( 线程池阻塞队列 | 线程池拒绝策略 | 使用 ThreadPoolExecutor 自定义线程池参数 )

    文章目录 一.线程池阻塞队列 二.拒绝策略 三.使用 ThreadPoolExecutor 自定义线程池参数 一.线程池阻塞队列 线程池阻塞队列是线程池创建的第 555 个参数 : BlockingQ ...

  2. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

  3. Java并发:线程池详解(ThreadPoolExecutor)

    前言 现在在实现异步时,基本都是使用线程池来实现,线程池在工作应用的还是比较频繁的,本文将就线程池的使用.相关原理和主要方法源码进行深入讲解学习. 线程池的基本使用 package com.joonw ...

  4. Java自带的线程池ThreadPoolExecutor详细介绍说明和实例运用

    Java 5 开始,Java 提供了自己的线程池.线程池就是一个线程的容器,每次只执行额定数量的线程. java.util.concurrent.ThreadPoolExecutor 就是这样的线程池 ...

  5. java线程池1001java线程池_深入浅出Java(Android )线程池ThreadPoolExecutor

    前言 关于线程池 在Java/Android开发中,设计到并发的请求,那基本上是离不开线程池了.用线程池的好处: 1.减少线程频繁创建.销毁的开销: 2.好控制并发量,降低OOM的可能,至于原因文中会 ...

  6. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】

    基于JDK1.8详细介绍了ThreadPoolExecutor线程池的execute方法源码! 上一篇文章中,我们介绍了:Java Executor源码解析(2)-ThreadPoolExecutor ...

  7. java线程池使用详解ThreadPoolExecutor使用示例

    一 使用线程池的好处 二 Executor 框架 2.1 简介 2.2 Executor 框架结构(主要由三大部分组成) 1) 任务(Runnable /Callable) 2) 任务的执行(Exec ...

  8. Java并发编程之线程池ThreadPoolExecutor解析

    线程池存在的意义 平常使用线程即new Thread()然后调用start()方法去启动这个线程,但是在频繁的业务情况下如果在生产环境大量的创建Thread对象是则会浪费资源,不仅增加GC回收压力,并 ...

  9. java修改线程池名称_自定义线程池的名称(ThreadPoolExecutor)

    目的:有时候为了快速定位出现错误的位置,在采用线程池时我们需要自定义线程池的名称. 1.创建ThreadFactory(ThreadPoolExecutor默认采用的是DefaultThreadFac ...

最新文章

  1. mysql与配偶同性_mysql 左,右,内连接
  2. 解决gradle下载慢的问题
  3. 操作系统【八】文件管理
  4. 前端学习(2185):tabberitem的颜色动态
  5. git 命令详解和Android Studio代码管理工具
  6. 习题3.4 最长连续递增子序列 (20 分) 数据结构 PTA
  7. Apple开源了用于ARM CPU的iOS内核
  8. cmake使用教程(五)调用opencv外部库和自己生成的库
  9. linux双机热备份
  10. 单片机入门教程:第一章 单片机8051概述
  11. 执行力强的人九个特点
  12. 世界五百强面试题计算机,世界五百强IT企业最新C++经典面试题及答案
  13. 在标准IO库中,rewind函数作用?
  14. LeetCode 885 救生艇
  15. 自制反汇编工具使用实例 其二(使用xmm寄存器初始化对象,以及空的成员函数指针)...
  16. Java多态1 - 引入多态,多态解决主人给不同动物喂食物的问题
  17. 分支语句(if,switch)
  18. 中山大学计算机在职研究生分数线,中山大学在职研究生的合格分数线不是很高吗...
  19. 上海亚商投顾:沪指放量涨1.69% 房地产板块掀涨停潮
  20. 诛仙手游服务器购买无限制,诛仙手游全新福利提升 摆摊及购买增加次数限制...

热门文章

  1. 【Java并发编程】之二:线程中断
  2. 数据结构-队列和栈的那些事(三)
  3. c# 整个工程(包括窗体工程)做成dll
  4. Eclipse中自动提示的方法参数都是arg0,arg1的解决方法
  5. 2015-2020年各类国际会议与期刊基于图像的三维对象重建论文综述(2)——Encoder stage
  6. Python3解决modulenotfounderror: no module named‘_bz2‘
  7. 业界分享 | 美团到店综合知识图谱的构建与应用
  8. 达摩院李雅亮:大规模预训练模型的压缩和蒸馏
  9. 站在BERT肩膀上的NLP新秀们(PART II)
  10. java string hash变量_java基础(六)-----String性质深入解析