济宁11项新产品入围省新产品(技术)重点推广目录_励志网

济宁11项新产品入围省新产品(技术)重点推广目录

2018-06-22 17:21 来源:励志网

在线程数量为4时,性能最优。因为此任务的类型是计算密集型的,只有4个CPU,因此线程数量为4时,达到最优情况。

privateclassForkJoinTaskextendsRecursiveTask{

publicForkJoinTask(intfirst,intlast){

对于任何线程池而言,它们的工作方式几乎都是相同的:

线程池在同时满足以下三个条件时,就会创建一个新的线程:

当计算是通过Servlet触发的时候,性能数据是下面这个样子的(LoadGenerator会同时发送20个请求):

设置了最大线程数之后,还需要设置最小线程数。对于绝大部分场景,将它设置的和最大线程数相等就可以了。

subCount+=right.join();

链接:blog.csdn.net/dm_vincent/article/details/39505977

#p#分页标题#e#任务队列是无限队列(UnboundQueue)无界限的队列可以是诸如LinkedBlockingQueue这种类型,在这种情况下,任何被提交的任务都不会被拒绝。但是线程池会忽略最大线程数这一参数,意味着线程池的最大线程数就变成了设置的最小线程数。所以在使用这种队列时,通常会将最大线程数设置的和最小线程数相等。这就相当于使用了一个固定了线程数量的线程池。

即使在CPU利用率达到最高时,基线百分比也不是理想中的25%,这是因为虽然在程序运行过程中,CPU资源并不是只被应用程序线程独享的,一些后台线程有时也会需要CPU资源,比如GC线程和系统的一些线程等。

在批处理程序中,最小线程数是否等于最大线程数并不重要。因为最后线程总是需要被创建出来的,所以程序的运行时间应该几乎相同。对于服务器程序而言,影响也不大,但是一般而言,线程池中的线程在“热身”阶段就应该被创建出来,所以这也是为什么建议将最小线程数设置的等于最大线程数的原因。

将最小线程数设置的小于最大线程数的初衷是为了节省资源,因为每多创建一个线程都会耗费一定量的资源,尤其是线程栈所需要的资源。但是在一个系统中,针对硬件资源以及任务特点选定了最大线程数之后,就表示这个系统总是会利用这些线程的,那么还不如在一开始就让线程池把需要的线程准备好。然而,把最小线程数设置的小于最大线程数所带来的影响也是非常小的,一般都不会察觉到有什么不同。

任务队列是有限队列(BoundedQueue)当使用的队列是诸如ArrayBlockingQueue这种有限队列的时候,来决定什么时候创建新线程的算法就相对复杂一些了。比如,最小线程数是4,最大线程数是8,任务队列最多能够容纳10个任务。在这种情况下,当任务逐渐被添加到队列中,直到队列被占满(10个任务),此时线程池中的工作线程仍然只有4个,即最小线程数。只有当仍然有任务希望被放置到队列中的时候,线程池才会新创建一个线程并从队列头部拿走一个任务,以腾出位置来容纳这个最新被提交的任务。

关于如何定制ThreadPoolExecutor,遵循KISS原则(KeepItSimple,Stupid)就好了。比如将最大线程数和最小线程数设置的相等,然后根据情况选择有限队列或者无限队列。

计算机的硬件情况

在ThreadPoolExecutor和其相关的类型中,最小线程数被称为线程池核心规模(CorePoolSize),在其它Java应用服务器的实现中,这个数量也许被称为最小线程数(MinThreads),但是它们的概念是相同的。

从上表中可以得到的结论:

当线程数为4时,达到最优性能,再增加线程数量时并没有更好的性能,因为此时CPU的利用率已经达到了最高,在增加线程只会增加线程之间争夺CPU资源的行为,因此反而降低了性能。

ForkJoinTaskleft=newForkJoinTask(first,mid);

在定制ThreadPoolExecutor时,遵循KISS原则,通常情况下会提供最好的性能。

对于ThreadPoolExecutor而言,当任务数量达到最大时,再尝试增加新的任务就会失败。ThreadPoolExecutor有一个rejectedExecution方法用来拒绝该任务。这会导致应用服务器返回一个HTTP状态码500,当然这种信息最好以更友好的方式传达给Client,比如解释一下为什么你的请求被拒绝了。

所以当使用ThreadPoolExecutor时,使用分治法会存在问题,因为ThreadPoolExecutor中的线程无法像任务队列中再添加一个任务并且在等待该任务完成之后再继续执行。而使用ForkJoinPool时,就能够让其中的线程创建新的任务,并挂起当前的任务,此时线程就能够从队列中选择子任务执行。

定制ThreadPoolExecutor

为了方便讨论,下面假设JVM有4个可用的CPU。那么任务也很明确,就是要最大程度地“压榨”它们的资源,千方百计的提高CPU的利用率。

#p#分页标题#e#下面,从Client的角度考虑一下问题,并发Client的数量对于Server的响应时间会有什么影响呢?还是同样地环境,当并发Client数量逐渐增加时,响应时间会如下发生变化:

从上面中得到一些结论:

在Java7中引入了一种新的线程池:ForkJoinPool。

当前线程池中所有的线程都处于工作状态

线程池有一个列表或者队列的数据结构来存放需要被执行的任务。显然,在某些情况下,任务数量的增长速度会大于其被执行的速度。如果这个任务代表的是一个来自Client的请求,那么也就意味着该Client会等待比较长的时间。显然这是不可接受的,尤其对于提供Web服务的服务器程序而言。

设置最大线程数

线程池和ThreadPoolExecutors

线程池往往拥有最小和最大线程数:

假设当任务是计算密集型的,意味着任务不需要执行IO操作,例如读取数据库,读取文件等,因此它们不涉及到同步的问题,任务之间完全是独立的。比如使用一个批处理程序读取Mock数据源的数据,测试在不线程池拥有不同线程数量时的性能,得到下表:

在上面的场景中,如果从ThreadPoolExecutor的角度进行考虑,那么在任务队列中一直会有任务处于挂起(Pending)的状态(因为Client的每个请求对应的就是一个任务),而所有的可用线程都在工作,CPU正在满负荷运转。这个时候添加线程池的线程数量,让这些添加的线程领取一些挂起的任务,会发生什么事情呢?这时带来的只会是线程之间对于CPU资源的争夺更加激烈,降低了性能。

性能下降的速度并不明显,这也是因为任务类型是计算密集型的缘故,如果性能瓶颈不是CPU提供的计算资源,而是外部的资源,如数据库,文件操作等,那么增加线程数量带来的性能下降也许会更加明显。

当Client数量增加时,你也许会想通过增加服务端线程池的线程数量来提高性能,可是在CPU密集型任务的情况下,这么做只会降低性能。因为系统的瓶颈就是CPU资源,冒然增加线程池的线程数量只会让对于这种资源的竞争更加激烈。

所以,在面对性能方面的问题时。第一步永远是了解系统的瓶颈在哪里,这样才能够有的放矢。如果冒然进行所谓的“调优”,让对瓶颈资源的竞争更加激烈,那么带来的只会是性能的进一步下降。相反,如果让对瓶颈资源的竞争变的缓和,那么性能通常则会提高。

publicclassForkJoinTest{

有任务需要被执行

任务被投放到一个队列中(队列的数量不定)

最合适的最大线程数该怎么确定,依赖以下两个方面:

当前线程池的线程数没有达到最大线程数

那么,最大线程数最少需要被设置成4,因为有4个可用的CPU,意味着最多能够并行地执行4个任务。当然,垃圾回收(GarbageCollection)在这个过程中也会造成一些影响,但是它们往往不需要使用整个CPU。一个例外是,当使用了CMS或者G1垃圾回收算法时,需要有足够的CPU资源进行垃圾回收。

protectedIntegercompute(){

最大线程数,当需要处理的任务过多时,线程池能够拥有的最大线程数。这样是为了保证不会有过多的线程被创建出来,因为线程的运行需要依赖于CPU资源和其它各种资源当线程过多时,反而会降低性能。

随着线程数量逐渐增加,性能下降,因为线程之间会互相争夺CPU资源,造成频繁切换线程执行上下文环境,而这些切换只会浪费CPU资源。

最小线程数,即当任务队列为空时,线程池中最少需要保持的线程数量,这样做是考虑到创建线程是一个相对耗费资源的操作,应当尽可能地避免,当有新任务被投入队列时,总会有线程能够立即对它进行处理。

那么到最后,所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。

它同ThreadPoolExecutor一样,也实现了Executor和ExecutorService接口。它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。

那么是否有必要将线程数量设置的更大呢?这就取决于任务的特征了。

所以,线程池会有机制来限制列表/队列中任务的数量。但是,和设置最大线程数一样,并没有一个放之四海而皆准的最优任务数量。这还是要取决于具体的任务类型和不断的进行性能测试。

for(inti=first;i>>1;

ForkJoinTaskright=newForkJoinTask(mid+1,last);

线程池中的线程数量必须仔细的设置,否则冒然增加线程数量只会带来性能的下降。

但是在对线程池进行规模变更(Resizing)的时候,ThreadPoolExecutor和其它线程池的实现也许存在的很大的差别。

一个最简单的情况是:当有新任务需要被执行,且当前所有的线程都被占用时,ThreadPoolExecutor和其它实现通常都会新创建一个线程来执行这个新任务(直到达到了最大线程数)。

设置最小线程数

虽然在程序中可以直接使用Thread类型来进行线程操作,但是更多的情况是使用线程池,尤其是在JavaEE应用服务器中,一般会使用若干个线程池来处理来自客户端的请求。Java中对于线程池的支持,来自ThreadPoolExecutor。一些应用服务器也确实是使用的ThreadPoolExecutor来实现线程池。

比如,我们需要统计一个double数组中小于0.5的元素的个数,那么可以使用ForkJoinPool进行实现如下:

subCount=left.join();

线程池是对象池的一个有用的例子,它能够节省在创建它们时候的资源开销。并且线程池对系统中的线程数量也起到了很好的限制作用。

线程完成任务后,继续尝试从队列中取得任务,如果队列为空,那么线程进入等待状态

因为任务类型是计算密集型的,当并发Client数量时1,2,4时,平均响应时间都是最优的,然而当出现多余4个Client时,性能会随着Client的增加发生显著地下降。

至于线程池会如何创建这个新的线程,则是根据任务队列的种类:

线程从队列中取得任务并执行

在一些场景中,也需要要设置一个不同的最小线程数。比如当一个系统最大需要同时处理2000个任务,而平均任务数量只是20个情况下,就需要将最小线程数设置成20,而不是等于其最大线程数2000。此时如果还是将最小线程数设置的等于最大线程数的话,那么闲置线程(IdleThread)占用的资源就比较可观了,尤其是当使用了ThreadLocal类型的变量时。

线程池任务数量(ThreadPoolTaskSizes)

任务队列是SynchronousQueue这个队列的特点是,它并不能放置任何任务在其队列中,当有任务被提交时,使用SynchronousQueue的线程池会立即为该任务创建一个线程(如果线程数量没有达到最大时,如果达到了最大,那么该任务会被拒绝)。这种队列适合于当任务数量较小时采用。也就是说,在使用这种队列时,未被执行的任务没有一个容器来暂时储存。

ForkJoinPool主要用来使用分治法(Divide-and-ConquerAlgorithm)来解决问题。典型的应用比如快速排序算法。这里的要点在于,ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序,那么会将这个任务分割成两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推,对于500万的数据也会做出同样的分割处理,到最后会设置一个阈值来规定当数据规模到多少时,停止这样的分割处理。比如,当元素的数量小于10时,会停止分割,转而使用插入排序对它们进行排序。

对于线程池的性能调优,最重要的参数就是线程池的大小。

责编: