一、什么是线程池:

线程池主要是为了解决 新任务执行时,应用程序为任务创建一个新线程 以及 任务执行完毕时,销毁线程所带来的开销。通过线程池,可以在项目初始化时就创建一个线程集合,然后在需要执行新任务时重用这些线程而不是每次都新建一个线程,一旦任务已经完成了,线程回到线程池中并等待下一次分配任务,达到资源复用的效果。

1、线程池的主要优势有:

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

二、创建线程池:

1、通过Executors创建线程池:

在JUC包中的Executors中,提供了一些静态方法,用于快速创建线程池,常见的线程池有:

(1)newSingleThreadExecutor:创建一个只有一个线程的线程池,串行执行所有任务,即使空闲时也不会被关闭。可以保证所有任务的执行顺序按照任务的提交顺序执行。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。

适用场景:需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程活动的应用场景。

(2)newFixedThreadPool:创建一个固定线程数量的线程池(corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作为阻塞队列)。初始化时线程数量为零,之后每次提交一个任务就创建一个线程,直到线程达到线程池的最大容量。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

适用场景:为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。

(3)newCachedThreadPool:创建一个可缓存的线程池,线程的最大数量为Integer.MAX_VALUE。空闲线程会临时缓存下来,线程会等待60s还是没有任务加入的话就会被关闭。

适用场景:适用于执行很多的短时间异步任务的小程序,或者是负载较轻的服务器。

(4)newScheduledThreadPool:创建一个支持执行延迟任务或者周期性执行任务的线程池。

2、ThreadPoolExecutor构造函数参数的说明:

使用Executors创建的线程池,其本质都是通过不同的参数构造一个ThreadPoolExecutor对象,主要包含以下7个参数:

 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 省略...}

(1)corePoolSize:线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列workQueue中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

(2)maximumPoolSize:线程池中允许的最大线程数。如果当前workQueue满了之后可以创建的最大线程数。

(3)keepAliveTime:空闲线程的存活时间。

(4)unit:keepAliveTime空闲线程存活时间的单位;

(5)workQueue:阻塞队列,用来存放等待被执行的任务,且任务必须实现Runnable接口,在JDK中提供了如下阻塞队列:

  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
  • LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
  • SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene
  • PriorityBlockingQuene:具有优先级的无界阻塞队列;
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

(6)threadFactory:线程工厂,主要用来创建线程,默认为正常优先级、非守护线程。

(7)handler:线程池拒绝任务时的处理策略。

3、不要使用Executors创建线程池:

阿里巴巴开发手册并发编程有一条规定:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这是为什么呢?主要是因为这样的可以避免资源耗尽的风险,因为使用Executors返回线程池对象的弊端有:

(1)FixedThreadPool 和 SingleThreadPool 允许的阻塞队列长度为 Integer.MAX_VALUE,这样会导致堆积大量的请求,从而导致OOM;

(2)CachedThreadPool 允许创建的线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

所以创建线程池,最好是根据线程池的用途,然后自己创建线程池。

三、线程池执行策略:

执行逻辑说明:

(1)当客户端提交任务时,线程池先判断核心线程数是否小于corePoolSize,如果是,则创建新的核心线程数运行这个任务;

(2)如果正在运行的线程数大于或等于corePoolSize,则判断workQueue队列是否已满,如果未满,则将任务放入workQueue中;

(3)如果workQueue队列已经满了,则判断当前线程池中的线程数量是否大于maximumPoolSize,如果小于maximumPoolSize,则启动一个非核心线程来执行任务;

(4)如果线程池中线程数量大于或等于maximumPoolSize,那么线程池会根据设定的拒绝策略,做出相应的措施。

  • ThreadPoolExecutor.AbortPolic(默认):抛出RejectedExecutionException异常;
  • ThereadPoolExecutor.CallerRunsPolicy:在当前正在执行的线程的execute方法中运行被拒绝的任务。
  • ThreadPoolExecutor.DiscardOldestPoliy:丢弃workQueue中等待最长时间的任务,并将被拒绝的任务添加到队列之中。
  • ThreadPoolExecutor.DiscardPolicy:将直接丢弃此线程。

(5)当一个线程完成任务时,它会从workQueue中获取下一个任务来执行。

(6)当一个线程空闲超过keepAliveTime设定的时间时,线程池会判断,如果当前线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

四、如何合理的配置线程池的大小?

1、一般都是根据任务类型来配置线程池大小:

  • 如果是 CPU 密集型:那么就意味着 CPU 是稀缺资源,这个时候通常不能通过增加线程数来提高计算能力,因为线程数量太多,会导致频繁的上下文切换,一般这种情况下,建议合理的线程数值 = CPU数 + 1,减少线程上下文的切换;
  • 如果是 IO 密集型:说明需要较多的等待,因为 IO 操作并不占用CPU,大部分线程都阻塞,所以可以多配置线程数,让CPU处理更多的业务,这个时候可以参考 Brain Goetz 的推荐方法,线程数 = CPU核数 × (1 + 平均等待时间/平均工作时间)。参考值可以是 N(CPU) 核数 * 2。

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

2、有界队列和无界队列的配置:

一般情况下配置有界队列,在一些可能会有爆发性增长的情况下使用无界队列。任务非常多时,使用非阻塞队列并使用CAS操作替代锁可以获得好的吞吐量。

五、Executor 框架:

1、什么是 Executor 框架:

Executor 是一个用于任务执行和调度的框架,目的是将任务的提交过程与执行过程解耦,使得用户只需关注任务的定义和提交,而不需要关注具体如何执行以及何时执行;其中,最顶层是 Executor 接口,它只有一个用于执行任务的 execute() 方法。Executor框架主要由3大部分组成:

  • (1)任务:实现 Callable 接口或 Runnable 接口的类,其实例就可以成为一个任务提交给 ExecutorService 去执行:其中 Callable 任务可以返回执行结果,Runnable 任务无返回结果。
  • (2)任务的执行:包括任务执行机制的核心接口 Executor,以及继承自 Executor 的 ExecutorService 接口。Executor框架的关键类ThreadPoolExecutor 也实现了 ExecutorService 接口;
  • (3)任务的异步计算结果:包括 Future 接口和实现 Future 接口的 FutureTask 类、ForkJoinTask 类。

2、使用步骤:

把任务,如 Runnable 接口或 Callable 接口的实现类提交(submit、execute)给线程池执行,如 ExecutorService、ThreadPoolExecutor 等。线程执行完毕之后,会返回一个异步计算结果 Future,然后调用 Future 的 get()方法等待执行结果即可,Future 的 get() 方法会导致主线程阻塞,直到任务执行完成。

其中 Runnable 任务无返回结果,Callable 任务可以返回执行结果,Callable 任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即 Future 可以拿到异步执行任务各种结果;在实际业务场景中,Future 和 Callable 基本是成对出现的,Callable 负责产生结果,Future 负责获取结果。
另外,还有一个 Executors 类,它是一个工具类,提供了创建 ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 对象的静态方法。

3、线程池中 submit() 和 execute() 方法有什么区别?

两个方法都可以向线程池提交任务,execute() 方法的返回类型是 void,它定义在 Executor 接口中, 而 submit() 方法可以返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩展自 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。

六、Java线程模型:Java线程与操作系统线程的关系:

现在 Java 线程的本质,其实就是操作系统中的线程,Java 线程的实现是基于一对一的线程模型,通过语言级别层面程序去间接调用系统内核的线程模型,即在使用 Java 线程时,JVM 是转而调用当前操作系统的内核线程来完成当前任务。内核线程就是由操作系统内核支持的线程,这种线程是由操作系统内核来完成线程切换,内核通过操作调度器进而对线程执行调度,并将线程的任务映射到各个处理器上。

由于我们编写的多线程程序属于语言层面的,程序不会直接去调用内核线程,取而代之的是一种轻量级的进程(Light Weight Process),也是通常意义上的线程,由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级进程调用内核线程,进而由操作系统内核将任务映射到各个处理器,这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。

实际上从 Linux 内核2.6开始,就把 LinuxThread 从 LWP 换成了新的线程实现方式NPTL,NPTL 解决了 LinuxThread 中绝大多数跟POSIX 标准不兼容的特性,并提供了更好的性能,可扩展性及可维护性等等。

Java 线程模型如下图所示,每个线程最终都会映射到CPU中进行处理,如果CPU存在多核,那么一个CPU将可以并行执行多个线程任务:

参考文章:

线程池源码解析:https://juejin.cn/post/6927456645169545230
Java线程和操作系统线程的关系:https://juejin.cn/post/6918559409496915982
全面理解Java内存模型及volatile关键字:https://blog.csdn.net/javazejian/article/details/72772461

JUC多线程:线程池的创建及工作原理 和 Executor 框架相关推荐

  1. java线程池的工作原理_Java 线程池的介绍以及工作原理

    在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1. 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 2. 提高响应速度 ...

  2. Java 线程池的介绍以及工作原理

    在什么情况下使用线程池? 1.单个任务处理的时间比较短 2.将需处理的任务的数量大 使用线程池的好处: 1. 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 2. 提高响应速度 ...

  3. 多线程线程池的基本创建,使用方法

    import java.util.concurrent.*;/*** 多线程线程池的基本创建,使用方法** @author silence*/ public class Silence {public ...

  4. 多线程线程池的实现java_如何在Java中实现线程池

    多线程线程池的实现java 线程是独立程序的执行路径. 在java中,每个线程都扩展java.lang.Thread类或实现java.lang.Runnable. 多线程是指在一个任务中同时执行两个或 ...

  5. Linux下通用线程池的创建与使用

    Linux下通用线程池的创建与使用 本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关.另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整 ...

  6. 操作系统:为什么IO操作不占用CPU却会导致进程阻塞?Web服务器每接收一个请求都会创建一个新的线程吗?Tomcat服务器工作原理?

    为什么IO操作不占用CPU却会导致进程阻塞?Web服务器每接收一个请求都会创建一个新的线程吗?这两个问题在我学操作系统以前我都挺困惑的.现在我来尝试着解答一下. 1. 为什么IO操作不占用CPU却会导 ...

  7. Java多线程- 线程池的基本使用和执行流程分析 - ThreadPoolExecutor

    线程池的实现原理 池化技术 一说到线程池自然就会想到池化技术. 其实所谓池化技术,就是把一些能够复用的东西放到池中,避免重复创建.销毁的开销,从而极大提高性能. 常见池化技术的例如: 线程池 内存池 ...

  8. 谈谈java的线程池(创建、机制)

    目录 Executors创建线程池默认方法 自定义线程池 Executors创建线程池默认方法 newFixedThreadPool()方法,该方法返回一个固定数量的线程池,该方法的线程数始终不变,当 ...

  9. java(线程池的创建方式,和线程池的原理)

    1.为什么要使用线程池:   减少资源消耗,通过重复的使用已创建好的线程,避免了线程的频繁创建和销毁所造成的消耗 提高响应速度,当任务到达的时候,不需要再去创建,可以直接使用已经创建好的线程就能立即执 ...

最新文章

  1. 吉林大学计算机学院男女,吉林大学非诚勿扰千人观看 22位男女同学一同相亲...
  2. 几万条数据的excel导入到mysql_【记录】2万多条数据的Excel表格数据导入mysql数据库...
  3. 对python文件方法open的探究
  4. logfile switch causes incremental checkpoint?
  5. 获取程序下基目录下的文件的
  6. Android ActivityManager 检测Service与Activity运行状态
  7. 剑指offer面试题14- I. 剪绳子(数学推导)
  8. 结构风荷载理论与matlab计算公式,结构风荷载理论与MATLAB计算
  9. tomcat编码配置gbk_tomcat的编码设置 tomcat中文问题的解决
  10. SQL Server Case表达式
  11. 位运算符(,|,~,^,同或,>>,<<)
  12. SQL Server 配置管理器中Browser灰色无法启动解决办法
  13. 作用域和自由变量的介绍
  14. AtCoder Beginner Contest 175 E.Picking Goods
  15. 【洛谷P1462】通往奥格瑞玛的道路
  16. 我是梦想橡皮擦,这是我在 CSDN 所有博客系列的清单(2022年3月22日更新)
  17. 【排序算法】冒泡排序、简单选择排序、直接插入排序比较和分析
  18. 苹果电脑如何开启桌面显示【硬盘】项目?
  19. 人工智能会超越人类智能吗?(Will Artificial Intelligence Surpass Human Intelligence?)
  20. linux系统ssh安装,安装ssh_Linux系统安装Autossh的方法

热门文章

  1. 十四、爬取天气气温,制作最低气温排行榜
  2. 五、吃掉Java基本数据类型,学习Java的基础
  3. 非凸函数上,随机梯度下降能否收敛?能,但有条件,且比凸函数收敛更难
  4. 向预训练进一步:掩码式非自回归模型训练
  5. java form action 参数_java发起form请求(有参数,无参数)
  6. oracle protocol=beq 不可用,学习笔记:Oracle数据库坏块 深入研究obj$坏块导致exp/expdp不能执行原因...
  7. spring原始注解
  8. java按位取反“~“运算符,负数右移
  9. Illegal access: this web application instance has been stopped already
  10. echarts——父元素宽度100%,但canvas宽度100px