线程池----合理的设置大小
直接创建大量线程的坏处
对于一个web服务器,服务器应用程序会处理来自客户端的请求。假设,每到达一个请求,我们的程序都为该请求创建一个线程来执行请求任务,那么这个创建的线程数目将会是无穷无尽的,“为每一个请求任务分配一个线程”,该做法是存在一些缺陷的,尤其是创建大量线程时:
(1)线程的生命周期的开销高:我们要明白线程的创建和销毁是需要代价的,如果说客户端请求的任务是很轻量级的,往往这种任务所执行的时间都很短并且做的事也很简单,那么为每一个请求创建线程,会消耗大量的计算机资源的。
(2)资源的消耗:一个活跃的线程会消耗系统的资源,特别是内存。当创建的线程太多时并且多过了CPU的核心数,必定会有线程会闲置。当大量线程闲置时,会占用大量的内存(极有可能会造成OOM异常),而且这么多的线程之间也会因为CPU的不足而存在竞争(线程经常得在用户态和内核态之间转换需要开销),所以当你的线程数如果达到了刚好使CPU处于忙碌,那么这时继续创建新线程,反而会降低性能。
线程池的作用
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:
(1)减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
(2)可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
那么在创建线程池时,我们又该如何设置好线程池的大小呢?
首先,设置一个线程池之前,我们先要知道线程所执行的任务,都是有不同的执行策略的:
(1)依赖性任务:大多数的任务都是不依赖于其他任务所执行的时序、时长、执行结果,但是如果一个任务执行需要依赖于其他的任务,那么该任务可以被认为是有依赖性的。
(2)线程封闭的任务:该任务在执行时,其他任务都不能执行,适合单线程池。
(3)响应时间敏感的任务:在上面的线程封闭任务,如果一个执行时间很长的任务交给单线程池去做,那么很长时间才会得到反馈,,所以时间敏感的任务不适合单线程模式。
对于依赖性很强的任务,要时刻注意线程池里的线程数目不能过小,因为线程数如果不够,A任务所依赖的B任务可能就要被放入等待队列,那么此时A任务就很可能执行不下去,最极端的情况可能就是B此时正好需要A任务所占用的线程,这时就会导致“线程饥饿死锁”。
那么如何设置线程池大小?
性质不同的任务可以交给不同规模的线程池执行。
任务的性质:
(1)CPU密集型
(2)I/O密集型
(3)混合型任务
对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,因为如果为CPU密集型的任务开启太多的线程,那么CPU为了调度好这些线程会消耗太多资源。而IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,所以尽管线程比较多,CPU也不用为了调度线程而花费太多开销,所以可以加大线程数量(但是也别太多)。
若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,这样CPU就会去处理其他的线程,所以才能更好的利用CPU。
当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
总结:
线程等待时间所占比例越高(线程此时很可能在等待依赖性任务完成或者处于IO阻塞,此时不占CPU,所以应该创建更多的线程让空闲的CPU去处理),需要越多线程。线程CPU时间所占比例越高,需要越少线程。
高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
(1)高并发、任务执行时间短的业务:因为时间短,很可能就是CPU密集型,所以线程不宜过多,和CPU核心数差不多就好。
(2)并发不高、任务执行时间长的业务要区分开看:
a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务 。
b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换 。
(3)并发高、任务执行时间又长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。