Java线程池

来源:互联网 发布:单片机最小系统各数值 编辑:程序博客网 时间:2024/05/29 18:58

前言:
关于虚拟机栈和本地方法栈,在 Java 虚拟机规范中描述了两种异常:
1、如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
2、如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。
每当启动一个新线程的时候,Java虚拟机都会为它分配一个java栈。java以栈帧为单位保存线程的运行状态。虚拟机只会对java栈执行两种操作:以栈帧为单位的压栈或者出栈。
java方法可以通过两种方式完成,一种通过return返回,成为正常返回;一种通过抛出异常而异常终止 。虚拟机都会将当前栈弹出java栈然后释放掉,这样上一个方法的栈帧就是当前栈帧了。
java栈数据是java线程所私有的。因此不存在多线程情况下栈数据访问同步的问题。

(一)线程池意义:
创建线程的代价较大,所以就是为了复用线程同时更好的控制线程的生命周期,减少在创建和销毁线程上所花的时间以及系统资源的开销,同时Executor框架把任务的提交和执行解耦,要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService,将得到一个Future对象,调用Future对象的get方法等待执行结果就好了,使用线程池可以进行统一的分配,调优和监控。使用线程池可以进行统一的分配,调优和监控线程,下面几点是摘自网上的观点。
1、现在服务器端的应用程序几乎都采用了“线程池”技术,这主要是为了提高系统效率。因为如果服务器对应每一个请求就创建一个线程的话,在很短的一段时间内就会产生很多创建和销毁线程动作,导致服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。线程池就是为了尽量减少这种情况的发生。
2、其实“线程池”就是用来存放“线程”的对象池。

在程序中,如果某个创建某种对象所需要的代价太高,同时这个对象又可以反复使用,那么我们往往就会准备一个容器,用来保存一批这样的对象。于是乎,我们想要用这种对象时,就不需要每次去创建一个,而直接从容器中取出一个现成的对象就可以了。由于节省了创建对象的开销,程序性能自然就上升了。这个容器就是“池”。很容易理解的是,因为有了对象池,因此在用完对象之后必须有一个“归还”的动作,这样便可以把对象放回池中,下次需要的时候就可以再次拿出来使用了。

例如,我们在使用ADO.NET连接SQL Server时,.NET框架就会自动帮我们维护一个连接池,这就是因为重新创建一个连接的代价相对比较高昂,“复用”就显得比较划算了。不过有些朋友可能会说,我们明明是每次都创建一个SqlConnection对象,哪里有“复用”啊?这是因为.NET框架中把“连接池”做透明了,对于程序员完全隐藏了这个概念。每次我们虽然创建的是新的SqlConnection对象,但是这个对象内部占用的“数据库连接”还是会复用的。为什么总是强调用完SqlConnection对象后要及时“关闭”(Dispose或Close)呢?其实这里并没有断开数据库连接,只是把这个连接放回了连接池。等到下次创建新的SqlConnection对象时,这个连接又可以拿出来用了。

既然我们每次都是从池中获取对象,那么这些对象是由谁来创建,又是什么时候创建的呢?这个就要根据不同情况由各对象池来自行实现了。例如,可以在创建对象池的时候指定池内对象数量,并且一下子全部创建好,当然您也可以在得到请求时,如果发现池中已经没有剩余对象时创建。您也可以“事前”先准备一部分,“事中”根据需要再继续补充。还可以做得“智能”一些,例如,根据实际情况添加或删除一些对象,甚至对需求“走势”进行“预测”,在空闲时便创建更多的对象以备“不时之需”。各中变化难以言尽。
当然,它们的原理和目的是类似的。相信上面这段文字也已经讲清了“线程池”的作用:因为创建一个线程的代价较高,因此我们使用线程池设法复用线程。就是这么简单。

(二)线程池结构:
每个线程池内部由几个模块组成:
1、一个任务队列,
2、一个工作线程的集合,
3、一个线程工厂,
4、管理线程状态的元数据。
这里写图片描述
(三)线程池内部几个主要的算法:

1、工作线程worker:即线程池中可以重复利用起来执行任务的线程,一个worker的生命周期内会不停的处理多个任务(即runnable)。线程池“复 用线程”的本质就是复用一个worker去处理多个任务,“流控“的本质就是通过对worker数量的控制实现并发数的控制。通过设置不同的参数来控制 worker的数量可以实现线程池的容量伸缩从而实现复杂的业务需求
2、存储等待任务的任务队列:工作者线程worker的数量是有限的,同一时间最多只能处理最多worker数量个任务。对于来不及处理 的任务需要保存到任务队列里,空闲的work会不停的读取任务队列里的任务进行处理。基于不同的队列实现,可以扩展出多种功能的线程池,如定制 队列出队顺序实现带处理优先级的线程池、定制队列为阻塞有界队列实现可阻塞能力的线程池等。流控一方面通过控制worker数控制并发数和处理能力,一方 面可基于队列控制线程池处理能力的上限。
3、线程池初始化:即线程池参数的设定和多个worker的初始化。通常有一开始就初始化指定数量的worker或者有请求时逐步初始化 工作者两种方式。前者线程池启动初期响应会比较快但造成了空载时的少量性能浪费,后者是基于请求量灵活扩容但牺牲了线程池启动初期性能达不到最优。
4、处理任务算法:任务给线程池添加任务时线程池的处理算法。有的线程池基于算法识别直接处理任务还是增加工作者数处理任务或者放入待处理队列,也有的线程池会直接将job放入待处理队列,等待工作者worker去取出执行。
5、worker的增减算法:业务线程数不是持久不变的,有高低峰期。线程池要有自己的算法根据业务请求频率高低调节自身工作者workers的 数量来调节线程池大小,从而实现业务高峰期增加工作者数量提高响应速度,而业务低峰期减少工作者数来节省服务器资源。增加算法通常基于几个维度进行:待处 理工作job数、线程池定义的最大最小工作者数、工作者闲置时间。
线程池终止逻辑:应用停止时线程池要有自身的停止逻辑,保证所有job都得到执行或者抛弃
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

(四)Executor
这里写图片描述
当提交一个新任务到线程池时,线程池的处理流程如下:
1、首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
2、其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
3、最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

shutDown()
当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。

shutdownNow()
根据JDK文档描述,大致意思是:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。
它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

0 0
原创粉丝点击