其实在真正的开发中,高并发的情形并直接用多线程,而是用线程池技术的地方比较多,线程的池化技术有不少好处,JDK也提供了线程池相关的类,下面将深刻展开进行介绍java
提升响应速度:当任务到达时,任务能够不须要等到线程建立就能当即执行nginx
假设一个服务器完成一项任务所需时间为:T1 建立线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 若是:T1 + T3 远大于 T2,则能够采用线程池,以提升服务器性能。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提升服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了
假设一个服务器一天要处理50000个请求,而且每一个请求须要一个单独的线程完成。在线程池中,线程数通常是固定的,因此产生线程总数不会超过线程池中线程的数目,而若是服务器不利用线程池来处理这些请求则线程总数为50000。通常线程池大小是远小于50000。因此利用线程池的服务器程序不会为了建立50000而在处理请求时浪费时间,从而提升效率
shutdown()
、submit()
的扩展,能够说是真正的线程池接口能够看到模版方法,钩子函数是源码中很是经常使用的设计模式
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
线程池中的核心线程数,当提交一个任务时,线程池建立一个新线程执行任务,直到当前线程数等于corePoolSize面试
线程池中会维护一个最小的线程数量,即便这些线程处理空闲状态,他们也不会 被销毁,除非设置了allowCoreThreadTimeOut
。这里的最小线程数量便是corePoolSize数据库
若是当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行小程序
若是执行了线程池的prestartAllCoreThreads()
方法,线程池会提早建立并启动全部核心线程设计模式
线程池中容许的最大线程数。若是当前阻塞队列满了,且继续提交任务,则建立新的线程执行任务,前提是当前线程数小于maximumPoolSize服务器
线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认状况下,该参数只在线程数大于corePoolSize时才有用多线程
keepAliveTime的时间单位并发
workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。经过workQueue,线程池实现了阻塞功能框架
用于保存等待执行的任务的阻塞队列,通常来讲,咱们应该尽可能使用有界队列,由于使用无界队列做为工做队列会对线程池带来以下影响:
1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,所以线程池中的线程数不会超过corePoolSize。
2)因为1,使用无界队列时maximumPoolSize将是一个无效参数。
3)因为1和2,使用无界队列时keepAliveTime将是一个无效参数。
4)更重要的,使用无界queue可能会耗尽系统资源,有界队列则有助于防止资源耗尽,同时即便使用有界队列,也要尽可能控制队列的大小在一个合适的范围
因此咱们通常会使用,ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue
建立线程的工厂,经过自定义的线程工厂能够给每一个新建的线程设置一个具备识别度的线程名,固然还能够更加自由的对线程作更多的设置,好比设置全部的线程为守护线程
Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”
线程池的饱和策略,当阻塞队列满了,且没有空闲的工做线程,若是继续提交任务,必须采起一种策略处理该任务,线程池提供了4种策略:
(1)AbortPolicy:直接抛出异常,默认策略
(2)CallerRunsPolicy:用调用者所在的线程来执行任务
(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务
(4)DiscardPolicy:直接丢弃任务
固然也能够根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务
实际上,JDK 的线程池已经为咱们预留的接口,在线程池核心方法中,有2 个方法是空的,就是给咱们预留的。还有一个线程池退出时会调用的方法。
能够看到,每一个任务执行先后都会调用 beforeExecute和 afterExecute方法。至关于执行了一个切面。而在调用 shutdown 方法后则会调用 terminated 方法
1)若是当前运行的线程少于corePoolSize,则建立新线程来执行任务(注意,执行这一步骤须要获取全局锁)
2)若是运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
3)若是没法将任务加入BlockingQueue(队列已满),则建立新的线程来处理任务
4)若是建立新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法
若是线程任务执行完,线程空闲数过多,那么超时参数开始起做用,超过超时时间,则多于corePoolSize
的线程会自动关闭,(keepAliveTime=0,表示当即中止),若是但愿corePoolSize
的线程空闲也自动关闭,经过allowCoreThreadTimeOut
接口进行设置
get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后当即返回,这时候有可能任务没有执行完能够经过调用线程池的shutdown
或shutdownNow
方法来关闭线程池。它们的原理是遍历线程池中的工做线程,而后逐个调用线程的interrupt方法来中断线程,因此没法响应中断的任务可能永远没法终止。可是它们存在必定的区别,shutdownNow
首先将线程池的状态设置成STOP,而后尝试中止全部的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown
只是将线程池的状态设置成SHUTDOWN状态,而后中断全部没有正在执行任务的线程
只要调用了这两个关闭方法中的任意一个,isShutdown
方法就会返回true
。当全部的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed
方法会返回true
。至于应该调用哪种方法来关闭线程池,应该由提交到线程池的任务特性决定,一般调用shutdown
方法来关闭线程池,若是任务不必定要执行完,则能够调用shutdownNow
方法。
要想合理地配置线程池,就必须首先分析任务特性,能够从如下几个角度来分析:
性质不一样的任务能够用不一样规模的线程池分开处理
CPU密集型任务应配置尽量小的线程,如配置Ncpu+1个线程的线程池。因为IO密集型任务线程并非一直在执行任务,则应配置尽量多的线程,如2*Ncpu。混合型的任务,若是能够拆分,将其拆分红一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。若是这两个任务执行时间相差太大,则不必进行分解。能够经过Runtime.getRuntime().availableProcessors()方法得到当前设备的CPU个数。
对于IO型的任务的最佳线程数,有个公式能够计算
Nthreads = NCPU UCPU (1 + W/C)
其中:
等待时间与计算时间咱们在Linux下使用相关的vmstat命令或者top命令查看
优先级不一样的任务可使用优先级队列PriorityBlockingQueue来处理。它可让优先级高的任务先执行
执行时间不一样的任务能够交给不一样规模的线程池来处理,或者可使用优先级队列,让执行时间短的任务先执行
依赖数据库链接池的任务,由于线程提交SQL后须要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU
建议使用有界队列。有界队列能增长系统的稳定性和预警能力,能够根据须要设大一点儿,好比几千
假设,咱们如今有一个Web系统,里面使用了线程池来处理业务,在某些状况下,系统里后台任务线程池的队列和线程池全满了,不断抛出抛弃任务的异常,经过排查发现是数据库出现了问题,致使执行SQL变得很是缓慢,由于后台任务线程池里的任务全是须要向数据库查询和插入数据的,因此致使线程池里的工做线程所有阻塞,任务积压在线程池里
若是当时咱们设置成无界队列,那么线程池的队列就会愈来愈多,有可能会撑满内存,致使整个系统不可用,而不仅是后台任务出现问题
线程数固定,且所有是核心线程,Integer.MAX_VALUE
长度的BlockingQueue
适用于为了知足资源管理的需求,而须要限制当前线程数量的应用场景,它适用于负载比较重的服务器
当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的
最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L
,意味着多余的空闲线程会被当即终止
FixedThreadPool使用无界队列LinkedBlockingQueue做为线程池的工做队列(队列的容量为Integer.MAX_VALUE
)
corePoolSize和maximumPoolSize被设置为1。其余参数与FixedThreadPool相同。SingleThreadExecutor使用界无队列LinkedBlockingQueue做为线程池的工做队列(队列的容量为Integer.MAX_VALUE
)
大小为0的BlockingQueue,能够起Integer.MAX_VALUE
个线程处理,适用于执行不少的短时间异步任务的小程序,或者是负载较轻的服务器
这里把keepAliveTime设置为60L
,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止
FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue做为线程池的工做队列。CachedThreadPool使用没有容量的SynchronousQueue做为线程池的工做队列,但CachedThreadPool的maximumPool是无界的。这意味着,若是主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断建立新线程。极端状况下,CachedThreadPool会由于建立过多线程而耗尽CPU和内存资源
利用全部运行的处理器数目来建立一个工做窃取的线程池,使用forkJoinPool实现
每一个线程有本身的等待队列,本身的队列处理完,会去其余线程尾端偷取任务来进行处理
使用工厂类Executors来建立。Executors能够建立2种类型的ScheduledThreadPoolExecutor,以下:
ScheduledThreadPoolExecutor适用于须要多个后台线程执行周期任务,同时为了知足资源管理的需求而须要限制后台线程的数量的应用场景。
SingleThreadScheduledExecutor适用于须要单个后台线程执行周期任务,同时须要保证顺序地执行各个任务的应用场景
quartz定时器框架 面试:假如提供一个闹钟服务,订阅这个服务的人特别多,10亿人,怎么优化?nginx把请求分到不一样服务器上,各个服务器上再用线程池+消息队列消费