美文网首页javaandroid
【并发编程】Java线程池详解

【并发编程】Java线程池详解

作者: 架构师修练手册 | 来源:发表于2019-03-31 20:38 被阅读24次

    线程池的作用想必不用多说,先来看一张java线程池的框架结构图。

    重点关注ThreadPoolExecutor类。

    ThreadPoolExecutor

    该类有四个构造函数,如下:

    先来解释下参数的意义,以参数最多的构造函数为例:

    corePoolSize和maximumPoolSize

    线程池中核心线程数和最大线程数。

    当workQueue为无界队列时,maximumPoolSize不起作用(超过核心线程数后任务全部会缓存到队列中,不会创建非核心线程)。

    线程池内部处理流程如下:

    keepAliveTime

    空闲线程的存活时间。

    当非核心线程空闲超过keepAliveTime后,该线程会被销毁,默认情况下,keepAliveTime只对超出corePoolSize的线程起作用。

    可以通过allowCoreThreadTimeOut(boolean)方法将空闲超时策略应用于核心线程,但是要保证超时时间不为0。

    unit

    keepAliveTime的单位。

    可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS)和毫微秒(NANOSECONDS)。

    workQueue

    用于缓存任务的阻塞队列。

    它是一个BlockingQueue,有如下几种:

    ArrayBlockingQueue:

    基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)排序元素,在创建队列时,就需要对其指定容量。

    LinkedBlockingQueue:

    基于链表结构的无界阻塞队列,此队列按FIFO (先进先出) 排序元素,与有界队列相比,除非系统资源耗尽,否则不存在任务入队失败的情况。

    SynchronousQueue:

    一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。

    PriorityBlockingQueue:

    具有优先级的无限阻塞队列,可根据任务自身的优先级顺序先后执行(总是确保高优先级的任务先执行)。

    DelayQueue:

    DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

    threadFactory

    创建线程的工厂,默认使用Executors.defaultThreadFactory()为线程池工厂。

    handler

    表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略,默认为AbortPolicy,即无法处理新任务时抛出异常。也可实现RejectedExecutionHandler接口来自定义拒绝策略。

    常用拒绝策略:

    1.AbortPolicy:直接抛异常;

    2.CallerRunsPolicy:用调用者所在线程来运行任务;

    3.DiscardOldestPolicy:丢弃队列最近的任务,并执行当前任务;

    4.DiscardPolicy:不处理,丢弃(但不抛出异常)。

    Executors

    经常有人将Executors,Executor,ExecutorService三者搞混,其实看结构图就可明白,ExecutorService继承了Executor,添加了一些控制线程生命周期的方法,算是扩展了Executor,而Executors根本没有出现在图中,因为Executors本身只是个工具类,用于快捷的创建线程池。

    Executors提供了六种创建线程池的方式,分别为fixedThreadPool,cachedThreadPool,singleThreadExecutor,scheduledThreadPool,singleThreadScheduledExecutor(单线程池和计划线程池的组合,将计划线程池的核心线程数置为1),workStealingPool。其中最后一种是基于ForkJoinPool实现的,暂不讨论。

    下面分别来看下几种线程池的实现。

    fixedThreadPool

    核心线程数和最大线程数为同一传入参数,即,所有线程均为核心线程,永不销毁,循环复用,当无空闲线程时,任务进入无界的LinkedBlockingQueue中。

    当任务量稳定,没有明显峰值和低谷,并且任务执行的时间不会太短的时候使用。适用于CPU密集型任务。

    cachedThreadPool

    核心线程数为0,最大线程数无界,超时时间为60s,使用SynchronousQueue队列(不会缓存任务)。即,每来一个任务,如果有空闲线程,则使用,如没有则创建,当线程空闲60s后立即销毁。

    适用于处理大量短时间偶发工作任务,即IO密集型任务。

    singleThreadExecutor

    核心线程数为1,最大线程数也为1,使用LinkedBlockingQueue为缓存队列。即,该线程池只有一个永不销毁的工作线程,如该线程被占用,则进入无界队列中等待。

    使用此线程池能保证所有的任务都是被顺序执行,最多会有一个任务处于活动状态。

    scheduledThreadPool

    核心线程数为传入参数(singleThreadScheduledExecutor将该参数指定为1),最大线程数无界,使用DelayedWorkQueue为阻塞队列。

    该线程池适用于定期或者延迟执行的任务。

    workStealingPool

    Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。

    总结

    如果让你选择,你会选择使用Executors方式创建还是使用ThreadPoolExecutor类去创建呢,我想大多数人会选择前者,因为方便快捷,Java提供的工具类,为什么不用呢?但是阿里规约却明确表示,禁止使用Executors创建线程池,原因如下:

    相关文章

      网友评论

        本文标题:【并发编程】Java线程池详解

        本文链接:https://www.haomeiwen.com/subject/emoubqtx.html