一文读懂java线程池

     2022-04-01     202

关键词:

为什么使用线程池

在生产环境中,我们经常面临这样的情况:一个请求的处理时间很短,但是请求的数量很大。

在这种情况下,如果为每个请求分别创建一个线程,那么OS可以使用有限的硬件资源来创建线程。这些操作,如切换线程状态和销毁线程,将消耗更少的资源进行业务处理。

因此,理想的处理方法是将请求中的线程数控制在一个范围内,不仅可以保证后续请求不会等待太长时间,还可以保证物理机为请求处理本身使用足够的资源。

线程池的设计与结构

开发人员通常使用ThreadPoolExecutor类的构造函数创建具有不同配置的线程池:

 

 通过这种施工方法,可以大致勾勒出水池的基本组成,其大致结构如下图所示。

注意:线程对象必须存在于线程池中,而不是要处理的任务中。因此,它被称为线程池,而不是任务池。线程池在池中分配一个空闲线程对象来运行提交的任务。
有几个元素组成了线程池:
等待队列:
要执行的任务队列。由于某些原因,线程池没有立即运行这些任务
芯线:
执行任务的线程对象数,由corepoolsize指定
非核心线程:
一旦任务数太大,线程池将创建非核心线程来帮助运行任务
注:不存在“核心线程”或“非核心线程”的概念。只是为了你的理解,但这是不同的。因此,两个概念都被引用。
线程池的常规工作流:
一个。开发人员提交要执行的任务。在接收到任务请求后,线程池具有以下处理条件:
当在frontline池中运行的线程数未达到核心池大小时,无论先前创建的线程是否空闲,线程池都将创建一个新线程来执行提交的任务。
当前处理池中运行的线程数达到核心池大小时,线程池将向等待队列添加任务,直到某个线程空闲,线程池将根据我们设置的等待队列规则从队列中取出一个新任务执行。
根据队列规则,此任务不能添加到等待队列中。此时,线程池创建一个“非核心线程”来直接运行任务。
注意,如果在这种情况下成功执行任务,则当前线程池中的线程数必须大于corepoolsize。
如果核心线程无法直接执行任务,无法加入等待队列,并且无法创建非核心线程,则线程池将根据拒绝处理器定义的策略处理该任务。
例如,在ThreadPoolExecutor中,如果未为线程池设置rejectedexecutionhandler。
此时,线程池将抛出rejectedexecutionexception异常,即线程池拒绝接受任务。
实际上,引发rejectedexecutionexception异常的操作是ThreadPoolExecutor线程池中的默认rejectedexecutionhandler实现。
2。一旦线程池中的线程完成执行任务,它将尝试从任务等待队列获取下一个等待任务(所有等待任务都实现BlockingQueue接口,这是一个可阻止的队列接口)。它将调用等待队列的轮询方法并停留在那里。
三个。当线程池中的线程数超过您设置的corepoolsize参数时,当前线程池中存在所谓的“非核心线程”。线程处理完任务后,如果在等待keepalivetime时间后没有分配新任务,则线程将被回收。当线程池回收线程时,它不会回收所谓的“非核心线程”,而是空闲时间达到keepalivetime阈值的线程,在线程池中的线程数等于您设置的corepoolsize参数之前,回收过程不会停止。
所谓的“核心线程”和“非核心线程”被同等对待。在进程池中的线程数等于您设置的corepoolsize参数之前,回收进程不会停止。

Executor 框架基本组成

使用 ThreadPoolExecutor 类的构造方法可以创建不同配置的线程池,但平时却很少使用。 大多数应用场景下,使用Java并发包中的 Executors 提供的5个静态工厂方法就足够了。

首先,来看看 Executor 框架的基本组成:

 

 

Executor是一个基本接口,只有一个execute(runnable)方法用于任务执行。它屏蔽了许多不相关的细节,如任务提交、线程创建和调度。
executorservice接口更加完善,提供了一些管理功能,如关闭线程池的关闭方法;还提供了更全面的任务提交机制,如使用submit方法提交任务,返回未来对象以获取任务执行结果;它甚至还具有批处理任务的功能,例如invokeall或invokeany和其他方法。
ThreadPoolExecutor、scheduledthreadpoolexecutor和forkjoinpool是java为满足复杂多变的应用场景而提供的几种基本线程池实现。
Executors是一个工具类,它提供各种静态工厂方法,从简化使用的角度创建具有不同配置的线程池。
使用ThreadPoolExecutor创建线程池
前面的内容给出了ThreadPoolExecutor的构造方法。这里,将详细解释构造方法的最后三个参数,以帮助您更好地理解和使用线程池。
线程池等待队列
只要实现了BlockingQueue接口的队列,就可以作为线程池的等待队列。例如,arrayblockingqueue、linkedblockingqueue、synchronousque、priorityblockingqueue、linkedtransferqueue等等。至于每个队列的区别和原理,我们将不在本文中讨论。
但互联网上的一些内容确实具有误导性。
Synchronousqueue也可以存储数据,它是一个无锁实现,但是size()方法直接返回0。因此,在比较synchronousqueue、linkedblockingqueue和linkedtransferqueue时,需要确认一些在线内容的正确性,这需要读者的关注。
Priorityblockingqueue将根据优先级对内部元素进行排序,优先级最高的元素将始终位于队列的头部。但是,它不能保证对具有相同优先级的元素进行排序,也不能保证当前队列中的元素(具有最高优先级的元素除外)的顺序正确。因此,这不是一个真正的命令。
线程池线程工厂
线程池最重要的任务之一是在一定的条件下创建线程。在ThreadPoolExecutor线程池中,创建线程的任务留给threadfactory。要使用线程池,必须指定threadfactory。如果未指定,则使用默认的threadfactory:defaultthreadfactory(此类位于executors工具类中)。

 

 

线程池拒绝策略
ThreadPoolExecutor线程池中还有一个重要接口:rejectedexecutionhandler。当任务提交到线程池时,线程池将拒绝处理该任务,并在以下情况下触发创建线程池时定义的拒绝策略:
新任务不能由线程池中的“核心线程”直接处理,也不能加入等待队列,也不能创建新线程来执行
线程池调用了shutdown方法以停止工作
线程池未处于正常工作状态
实际上,rejectedexecutionhandler接口有四种实现,可以直接在ThreadPoolExecutor中使用:
呼叫策略:
直接在非线程池外部调用此任务的run方法
丢弃策略:
在没有任何提示的情况下放弃被拒绝的任务
丢弃策略:
放弃等待队列头的任务,并将当前被拒绝的任务提交给线程池执行
中止策略:
拒绝任务并引发rejectedexecutionexception异常
其中,callerRunPolicy直接调用任务的run方法,可能导致线程安全问题;discardpolicy默默忽略被拒绝的任务,不输出日志或任何提示,开发人员无法知道线程池处理中的错误;discardoldestpolicy看起来很科学,但是,如果等待队列中存在容量问题,许多任务将被直接丢弃。此时,业务将出现bug,但开发人员很难找到它。
因此,更科学的方法是abortpolicy提供的处理方法:抛出异常并让开发人员处理它。当然,在特殊情况下,我也建议使用自定义拒绝策略。您可以缓存要重新发现的任务,也可以向MQ发送消息以通知业务端。
展开ThreadPoolExecutor线程池
ThreadPoolExecutor中提供了三个方法用于子类重写。它们可以帮助处于联机流程池处理任务的不同阶段的开发人员执行其他业务处理操作:
执行前:
当线程池即将开始执行任务时,线程池将触发此方法的调用。
执行后:
当线程池完成任务的执行时,线程池将触发此方法。
结束:
当线程池本身停止执行时调用此方法。
execute和submit方法的区别
ThreadPoolExecutor提供了两个方法execute和submit来提交任务,其中:
执行:提交的任务实现runnable接口。任务没有任何返回值。因此,无法获得执行结果。
提交:提交的任务实现可调用接口。任务完成后,返回执行结果。
当然,submit方法也可以提交任务来实现runnable接口,但它的处理方式与execute方法完全不同:submit方法提交的任务来实现runnable接口,将封装在executors.callable方法在线程内创建的runnableadapter对象中池,runnableadapter从可调用的继承。
线程池实践
了解执行器创建的线程池
如果使用执行器创建线程池,请确保了解每个方法创建的线程池的配置。
例如,对于newcachedthreadpool方法创建的线程池,其corepoolsize=0,maximumpoolsize=integer.max,而其等待队列是synchronousqueue,因此无法缓冲数据。它将尝试缓存线程并重用它们。当没有可用的缓存线程时,将创建一个新的工作线程。如果一个线程空闲超过60秒,它将被终止并移出缓存。当它长时间空闲时,这个线程池不会消耗任何资源。但是,需要注意的是,任务提交速度过快不仅会导致线程数量急剧增加,还会增加程序oom的风险。
在newfixedthreadpool(int nthreads)方法中,corepoolsize=maximumpoolsize=nthreads。任何时候最多有n个线程处于活动状态,这意味着如果任务数超过活动队列数,则工作队列中将出现空闲线程;如果工作线程退出,则将创建新的工作线程以补充指定的n个线程数。
在实际的应用场景中,许多人可能会滥用这些方法。因此,Alibaba Java规范建议使用ThreadPoolExecutor构造方法而不是executors。
不要随意创建线程池
从应用程序或服务的角度出发,我们可以对整个服务中线程的用途进行分类,并为每个分类创建适当的线程池。
我看过很多代码。只要使用线程,执行器就用来创建thr

一文读懂java多线程

一文读懂JAVA多线程背景渊源摩尔定律提到多线程好多书上都会提到摩尔定律,它是由英特尔创始人之一GordonMoore提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提... 查看详情

一文读懂java多线程

一文读懂JAVA多线程背景渊源摩尔定律提到多线程好多书上都会提到摩尔定律,它是由英特尔创始人之一GordonMoore提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提... 查看详情

读懂java线程池

在日常的工作当中,线程池是我们经常使用的。但是我们在使用过程中并没有考虑过会不会有什么问题,我们配置的参数是否正确,到底应该如何配置线程池的各个参数,才能使机器发挥最大的性能。所以根据作... 查看详情

java并发一文读懂(代码片段)

目录线程实现方式方式一:实现Runnable接口方式二:实现Callable接口方式三:继承Thread类演示功能代码实现接口VS继承Thread线程机制Executor(线程执行容器)Executor(线程执行容器)实现CachedThreadPool实例Fi... 查看详情

一文简单理解java线程池的问题(代码片段)

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。... 查看详情

java中的线程池如何实现,一文彻底搞懂(代码片段)

前言为什么要用线程池一键获取线程相关资料,还可获取最新java面试真题库在HotSpotVM的线程模型中,Java线程被一对一映射为内核线程。Java在使用线程执行程序时,需要调用操作系统内核的API,创建一个内核线程&... 查看详情

一文搞懂线程池中的执行原则和核心配置参数

  本文介绍下线程池的一些配置信息。  在软件开发中,池一直都是一种非常优秀的设计思想,通过建立池可以有效的利用系统资源,节约系统性能。Java中的线程池就是一种非常好的实现,从JDK1.5开始Java提... 查看详情

java并发关键字大练兵—一文读懂各个关键字(代码片段)

本文介绍了Threadlocal、volatile、condition、Semaphore、CountDownLatch、unsafe等关键字目录如下:Threadlocal本地线程volatileconditionCountDownLatch闩锁CyclicBarrier篱栅Semaphore信号灯unsafe魔法类StampedLock新读写锁1.Threadlocal从名字我们 查看详情

java并发关键字大练兵—一文读懂各个关键字(代码片段)

本文介绍了Threadlocal、volatile、condition、Semaphore、CountDownLatch、unsafe等关键字目录如下:Threadlocal本地线程volatileconditionCountDownLatch闩锁CyclicBarrier篱栅Semaphore信号灯unsafe魔法类StampedLock新读写锁1.Threadlocal从名字我们就可以看到Thr... 查看详情

一文带你了解java线程池(executor)-上(代码片段)

分析Java线程池就离不开Executor类,今天就让我们来一起好好看下除开今天要讲的线程池,我还整理了一些技术资料和面试题集,供大家提升进阶,面试突击,不管你是有跳槽打算还是单纯精进自己,都可以... 查看详情

一文读懂java异常处理

JAVA异常类型结构Error和Exeption受查异常和非受查异常异常的抛出与捕获直接抛出异常封装异常并抛出捕获异常自定义异常try-catch-finallytry-with-resource阿里巴巴异常处理规约常见面试题JAVA异常类型结构Throwable是所有异常类型的基类... 查看详情

一文读懂java异常处理

 JAVA异常类型结构Error和Exeption受查异常和非受查异常异常的抛出与捕获直接抛出异常封装异常并抛出捕获异常自定义异常try-catch-finallytry-with-resource阿里巴巴异常处理规约常见面试题 JAVA异常类型结构Throwable 是所有异... 查看详情

一文读懂高性能网络编程中的i/o模型

...式已经无能为力。本文(和下篇《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》)旨在为大家提供有用的高性能网络编程的I/O模型概览以及网络服务进程模型的比较,以揭开设计和实现高性能网络架构的神秘面... 查看详情

一文读懂kafka消息拉取机制|线程拉取模型

温馨提示:本文旨在详细介绍kafka消息拉取机制,以源码分析为手段,进行思考、归纳与总结,如果您对源码分析不感兴趣,可以直接阅读本文对第二部分,直到kafka消息拉取模型。在详细介绍Kafka拉取之前... 查看详情

一文读懂什么是java中的自动拆装箱

基本数据类型基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。Java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。Java基本类... 查看详情

一文读懂java中的代理模式(代码片段)

代理(Proxy)模式是我们在工作中广泛使用的设计模式之一,提供了对目标对象额外的访问方式。通过代理对象来访问目标对象,可以对目标对象进行功能的增强,即扩展目标对象的功能。例如在Spring中,AOP就是使用动态代理来... 查看详情

一文搞懂java的多线程底层逻辑,再也不怕多线程了(代码片段)

目录1、线程是什么2、启动线程3、线程池4、线程池的创建通过Executors工厂方法创建通过构造函数创建5、调试线程6、synchronized关键字没什么想说的,就是想写两次Java的,顺便送两本书,好了,开始吧1、线程是什... 查看详情

一文搞懂java的多线程底层逻辑,再也不怕多线程了(代码片段)

目录1、线程是什么2、启动线程3、线程池4、线程池的创建通过Executors工厂方法创建通过构造函数创建5、调试线程6、synchronized关键字没什么想说的,就是想写两次Java的,顺便送两本书,好了,开始吧1、线程是什... 查看详情