五种线程池的使用场景
-
newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
-
newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
-
newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
-
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
-
newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
-
newWorkStealingPool是jdk1.8才有的,会根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层用的ForkJoinPool来实现的。ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。
-
https://mp.weixin.qq.com/s/DFh86Kp83yMRRFAHJcyspg
Executor框架主要包含了3个方面:
-
任务:与java线程的任务相同,Executor框架接收实现了Runnable与Callable接口的任务。
-
执行线程池:在Executor框架下有两个核心接口,Executor接口与继承了Executor接口的ExecutorService接口,而ThreadPoolExecutor线程池正是这两个接口的实现类。此外Executor框架下还有AbstractExecutorService,ScheduledExecutorService,ScheduledThreadPoolExecutor的实现,这些实现类实现了Executor框架下不同的并发任务执行机制。
-
异步计算任务的支持:ExecutorService接口提供了Future的支持,此外Executor框架针对异步计算,提供了Future的实现类FutrueTask类。FutureTask是一个可取消的异步计算类(FutrueTask.cancel()),除了提供了Futrue类的基本实现外,还提供了查看计算任务是否执行状态,以及等待返回计算结果的方法FutrueTask.get()。
Executors基于ThreadPoolExecutor封装了3种模板线程池:FixedThreadPool、SingleThreadPool和CachedThreadPool。三种线程池都是直接使用ThreadPoolExecutor创建的,只是设置了不同的线程数以及任务队列,从而使得线程池具有不同的运行特性直接使用ThreadPoolExecutor来创建的固定线程池,三种线程池对应的特性与默认参数如下表所示:
线程池 | 特性 | 任务队列(BlockingQueue) | 核心线程数 | 最大线程数 | 线程保活时间 |
---|---|---|---|---|---|
FixedThreadPool | 创建固定线程数线程池 | LinkedBlockingQueue() | nThread | 无效 | 0L |
SingleThreadPool | 单线程任务执行线程池 | LinkedBlockingQueue() | 1 | 1 | 0 |
CachedThreadPool | 动态分配线程数 | SynchronousQueue() | 0 | Integer.MAX_VALUE | 60s |
SingleThreadPool:该线程池只创建了一个线程进行任务的执行,工作线程被创建后会不断的从任务队列中取任务,由于其KeepAliveTime也被设置为0,因此当任务队列为空时,该线程同样会退出,也就是说与FixedThreadPool相同,工作线程一旦创建就不会是空闲线程。同时SingleThreadPool使用了具有FIFO特性的无界队列LinkedBlockingQueue作为线程池任务队列,说明SingleThreadPool线程池可以保证任务的执行顺序与任务的添加顺序是相同的,由于只有一个线程会取任务,所有该线程池可以保证任务执行的顺序性。
FixedThreadPool:该线程池使用了固定线程数来创建线程池,其最大线程数等于核心线程数的原因在于使用了无界队列LinkedBlockingQueue,永远不会被填满(除非虚拟机抛出内存异常OOM),因此最大线程数只要设置不小于核心线程数即可,这也意味着FixedThreadPool不会拒绝任务的提交。KeepAliveTime表示线程池中空闲线程等待任务的最长时间,而FixedThreadPool将KeepAliveTime设置为0,意味着只要运行线程无法从队列中取到任务,便会立即结束本线程,从而线程池中不会存在空闲线程。FixedThreadPool确保了线程池的最大线程创建数量,一般应用于需要限制线程数的应用场景,但需要注意,无界队列的使用,可能会造成大量的任务存储在任务队列中,JVM虚拟机的退出,会使得这些任务的丢失,因此在使用时需要考虑到线程池异常关闭的已提交的任务处理机制。
CachedThreadPool:该线程池使用了一个不存储元素的阻塞队列SynchronousQueue作为任务队列,SynchronousQueue队列是一个没有容量的队列,那么结合线程池最大线程数与核心线程数的关系,我们可以这样理解,在线程池中没有空闲线程从SynchronousQueue中取任务时,向任务队列中提交任务的方法就会失败,此时ThreadPoolExecutor会采用处理任务队列已满的情况来创建新的工作线程去处理新的任务。这就意味着,如果向线程池提交的都是长任务,CachedThreadPool有可能会一直创建新的线程来执行新的任务,所以CachedThreadPool适合于小任务的并发应用场景。此外CachedThreadPool允许空闲线程存活60s,相对于其他两种线程池,CachedThreadPool的线程复用率更高。
定时任务执行线程池
ScheduleThreadPoolExecutor是ThreadPoolExecutor的一个子类,用于执行需要延时执行,或者周期执行的任务,提供了如下4个接口用于添加定时任务。当然,ScheduleThreadPoolExecutor保留了executor()与submit()方法,其提交的任务会被立即执行,不会等待。使用的是DelayedWorkQueue做为队列
https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247483747&idx=1&sn=e2ec215f2c4d5671e821258fd060d0a0&chksm=e8fe9d6adf89147c2cfabf6ce7449fec5e593c9bf733c824579d2823fd8777167d2f5483fba4&scene=21#wechat_redirect
Future:
JDK 1.5以后,提供了Callable与Future,是调用者可以获得任务执行完成后返回的结果。
Future适用于并发执行且需要返回值后继续处理的场景
https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247483752&idx=1&sn=2a3d418713f7abe7f4b2919893bbdc8a&chksm=e8fe9d61df891477aea56316f92e27b9114dc7181e8d05e8ee0ca91148a2b6a7e28e3336ac5f&scene=21#wechat_redirect
线程的创建与销毁,以及运行线程上下文切换都是需要消耗cpu资源的,相对来说任务的执行所占整个线程运行的cpu时间越短,线程的运行效率也相应越低。为了解决这样的问题,编程领域设计了线程池来解决线程切换带来的性能损耗。
线程池的设计思想是创建一定数量的运行线程,将要执行的任务,放入线程池,线程池会自动分配线程去执行任务,执行完任务的线程又会被放入池中,等待新任务的到来,而不是退出线程,从而实现了线程的重复利用,避免了系统反复创建销毁线程,造成的性能损耗。
创建线程池:
new ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数 | 意义 |
---|---|
corePoolSize | 线程池核心线程数量 |
maximumPoolSize | 线程池最大线程数量 |
keepAliveTime | 线程保持时间,空闲线程可以存活时间 |
TimeUnit | 线程保持时间的单位(keepAliveTime的单位) |
workQueue | 任务队列 |
threadFactory | 线程创建工厂 |
RejectedExecutionHandler | 线程数超过最大线程数后,任务将被拒绝并回调的handler |
向线程池提交任务,不需要返回值
threadPool.execute( new Runnable(){
public void run(){
...//任务代码
}
} )
向线程池提交任务,需要返回值:
Future<Object> threadFuture = threadPoolExecutor.submit(task);
try{
Object resualt = threadFuture.get();
}catch (InterruptedException e){
// 处理线程中断异常
}catch (ExecutionException e){
// 处理无法执行异常
} finally {
threadPoolExecutor.shutdown();
}
ThreadPoolExecutor的执行流程:
-
创建线程池,等待任务执行。
-
当任务提交给线程池后,会判断核心线程池是否已满,即当前线程数与corePoolSize进行比较,如果核心线程池未满,则创建新线程来执行任务,如果核心线程池已满则将任务加入任务队列BlockingQueue中,等待执行。
-
如果任务队列也满了,则ThreadPoolExecutor会继续创建新的线程来处理任务,但是线程池中线程数目不得超过最大线程数maximumPoolSize,否则线程池将会采取饱和策略,拒绝处理任务,并将调用用户设置的RejectedExecutionHandler策略函数进行处理。这里需要注意,只有BlockingQueue为有界队列时,maximumPoolSize参数才会有作用,否者无界BlockingQueue不可能满,不会触发线程池来处理任务队列已满的情况,无界队列使用不当可能造成线程池无休止创建线程的现象。
-
线程池中的线程处理完当前任务后,会从任务队列中尝试取任务,如果取到任务,则执行任务,否则等待keepAliveTime时间,如果在keepAliveTime内都没有取到任务,则该线程会退出。
线程池的饱和策略
饱和策略是线程池应对任务队列和线程池饱和时所采取的策略,ThreadPoolExecutor提供了setRejectedExecutionHandler()方法设置自定义饱和策略的接口,如果没有设置该接口,Java便会采取默认饱和策略AbortPolicy才处理,JDK提供了4中饱和策略:
-
AbortPolicy : 默认饱和策略,直接抛出异常。
-
CallerRunsPolicy : 使用调用者线程来执行任务。
-
DiscardOldestPolicy : 丢弃队列中最近一个任务,并执行当前任务。
-
DiscardPolicy : 不处理,直接丢弃当前任务。
这四种JDK提供的饱和策略都实现了RejectedExecutionHandler接口,并且只有AbortPolicy策略才会抛出RejectedExecutionException异常,如果实际开发环境中需要实现自定义饱和策略,可以参考以上四种饱和策略的实现方式。
关闭线程池:
-
shutdown():当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
-
shutdownNow():线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep/wait/Condition/定时锁等应用,interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
在调用shutdown()时,shutdown()只会将空闲线程进行关闭,而shutdownNow()方法会尝试关闭所有线程,因此如果任务是否正常执行完,对于系统没有影响,可以使用shutdownNow()方法,一般开发中都会使用shutdown()来优雅的关闭线程池。
线程池的配置原则:
CPU密集型的少启几个线程。
IO/DB等访问多少的多启几个线程。
https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=2247483731&idx=1&sn=63e3e87141a3e4a916473f3b327fad1c&chksm=e8fe9d5adf89144cf543f1824e7a7d4ecbac0783e0efd490894baef2100c95685e50637c3b84&scene=21#wechat_redirect
http://www.jianshu.com/p/87bff5cc8d8c
ThreadFactory的作用和常见使用方法
为了能够设置一个更有意义的线程名.
自主选择线程类型:守护线程或用户线程
线程优先级
处理未捕捉的异常
https://blog.youkuaiyun.com/androidzhaoxiaogang/article/details/8991039
https://blog.youkuaiyun.com/lovewithbeauty/article/details/50057469