线程池
引言
上一个文章说多线程的实现方法有四个,其中第四个有提到ExectorService接口与Callable与Runnable,Futrue等的一起实现,ExectorService是线程的管理工具。它一般有两个实现类,AbstractExecutorService, ScheduledThreadPoolExecutor
,其中 ThreadPoolExecutor
继承AbstractExecutorService
,我们通常用 ThreadPoolExecutor
来实现线程池。
线程池解决的问题
线程池产生的原因及好处
我们之前说起多线程的时候都是需要一个线程就开启一个线程来处理任务,就是我们通常的new thread(),主要会有下面的一些缺点:新建对象的性能比较差,缺乏统一管理,无限制的创建线程,相互竞争,占用过多的系统资源导致死机。
特别是:当处理任务的时间小于创建线程和销毁线程的时间和,那么频繁的创建线程就会很占用cpu,所以我们想到复用的方法来处理任务和线程之间的“矛盾”,应运而生的便是线程池。
重用存在的线程,减少对象的创建,消亡的开销,性能佳;有效控制最大的并发线程的数量,提高资源的使用率,避免过多资源竞争,避免堵塞;提供定时执行,定期执行,单线程,并发数的控制。
线程池
池子里有最多可以容纳N个线程,一般都存在的线程个数有M个(M<=N),在这M个线程中可能有正在运行的线程,可能有空闲的线程,我们提供一个外部传参的方法,把任务传进来,然后把需要处理的任务存进一个数据结构,比如队列里,中间会有个通过线程管理工具对任务进行分配给空闲的线程,其实就会有这么几种情况:
1.任务进来的时候,有空闲的线程或者线程数目没有达到最大的时候,我们可以直接不用存在队列就可以利用线程处理或者创建线程处理队列;
2.当任务来的时候,任务队列还没有存满那就直接的存在任务队列中;
3.任务队列已经存满了,那就可能抛弃旧有的任务来存储新来的任务。
此队列是阻塞队列
线程池的状态含义
running
接收新的任务并且处理阻塞队列里的新任务
shutdown
拒绝新的任务但是处理阻塞队列里的任务
stop
拒绝新任务并且抛弃阻塞队列里的任务,中断正在处理的任务
tidying
所有的任务都处理完之后当前线程池活动的线程数为0,将调用terminated方法
terminated
终止状态
线程池参数
线程个数参数
corepoolsize
:线程池核心线程个数,其实就是上面说的N。线程的基本大小,即在没有任务需要执行的时候线程池的大小。
maximunpoolsize
:线程池最大的线程数量,当队列满,并且当前线程个数小于maximunpoolsize
时,就会创建新的线程来执行任务。
largestpoolsize
:该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。
poolsize
:线程池中当前线程的数量。
存活时间
keepalivetime
:存活时间,若是当前线程池中的线程数量大于核心线程的数量,并且是闲置状态,这就是这些闲置线程能存活的最大时间。当keepalivetime=0
时,证明只要当前线程个数大于核心线程个数,并且当前线程闲置则回收。
线程池种类(使用样例)
newSingleThreadExecutor
:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行。
newFixedThreadPool
:创建定长线程池,可控制最大并发数,超出的线程会在队列中等待,当keepalivetime=0
时,只要当前线程个数大于核心线程个数,并且当前线程闲置则回收;
newCachedThreadPool
:创建一个按需创建线程的线程池,初始线程个数为0,阻塞队列为同步队列,keepalivetime
=60,当前线程只要空闲60秒就需要被回收;
newScheduledThreadPool
:创建一个定长线程池,支持定时及周期性任务执行;
Executors工厂类
提供创建线程池的几种方法
先创建线程池——>调用submit将线程对象提交到线程池中,不需要提交线程了就调用shutdown()
关闭线程池。
submit
的原型在父类AbstractExecutorService
里。
线程池的调优策略
参考:https://www.cnblogs.com/jianzh5/p/6437315.html
kiss原则
keep it simple,stupid
将最小的线程数和最大线程数设置为相同,保存任务方面,若适合无界队列,则选择LinkedBlockingQueue
;若适合有界,则ArrayBlockingQueue
设置最小线程数
一般而言,对于线程数为最小值的线程池,一个新线程一旦创建出来,至少应该保留几分钟,以处理任何负载飙升。空闲时间应该以分钟计,而且至少在10分钟到30分钟之间,这样可以防止频繁创建线程。
设置最大线程数
取决于负载特性以及底层硬件。特别是,最优线程数还与每个任务阻塞的频率有关。
线程池任务大小
当有空闲线程的时候从队列拉出一个任务,队列的任务可能特别大,则,后面的任务可能等待的时间就会相当的漫长,直到前面的任务执行完毕。
关于任务队列的大小只能靠我们真是的测量。
任务队列
创建线程池的时候创建最小的线程数,若是来一个任务就看有没有空闲的线程处理,若是有则直接处理,若是没有则判断是否达到最大的线程数,没有就创建线程处理,若是达到了,就加入等待的队列中,若是无法添加任务则拒绝此任务。
同步队列:SynchronousQueue
线程池的行为和我们预期的一样,它会考虑线程数:如果所有的线程都在忙碌,而且池中的线程数尚未达到最大,则会为新任务启动一个新线程;然而这个队列没办法保存等待的任务:如果来了一个任务,创建的线程数已经达到最大值,而且所有的线程都在忙碌,则新的任务都会被拒绝,所以如果是管理少量的任务,这是个不错的选择,对于其他的情况就不适合了。
无界队列:LinkedBlockingQueue
不会拒绝任何任务(因为队列大小没有限制)。这种情况下,ThreadPoolExecutor最多仅会按照最小线程数创建线程,也就是说最大线程池大小被忽略了。如果最大线程数和最小线程数相同,则这种选择和配置了固定线程数的传统线程池运行机制最为接近。
有界队列:ArrayBlockingQueue
如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。