多线程与并发-线程池-ThreadPoolExecutor

多线程与并发-线程池-ThreadPoolExecutor

概述:
线程池这东西很常见,使用的原因更好的对线程进行管理,就是减少线程创建销毁的开销。
虽然有几个现成的创建方法,但很多公司都不建议使用,而要求一定要通过ThreadPoolExecutor来创建,明确运行规则,指定更合适的参数。
这是一个参数最全的重载方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

一共有7个参数
corePoolSize,核心线程数
当有任务进来的时候,如果当前线程数未达到corePoolSize个数,则创建核心线程。
特点:

  • 当线程数未达到最大值的时候,新任务进来,即时有空闲线程也不会复用,仍然创建新的线程。
  • 核心线程一般不会被销毁,即使处于空闲状态,但是可以通过allowCoreThreadTimeOut(boolean value) 设置为 true ,这时核心线程的空闲时间超过keepAliveTime设定的时间时会被销毁

maximumPoolSize,最大线程池大小
线程池的最大的大小。
当核心线程已满,并且阻塞队列已满时,将判断当前线程数量是否达到最大值,如果未达到则创建一个非核心线程加入线程池,当线程数量>corePoolSize的时候,如果有线程的空闲时间超过keepAliveTime的时候将被销毁

keepAliveTime,超时时间
就是上述两个参数中提到的空闲时间,也就是线程最大空闲时间,默认用于非核心线程,通过 allowCoreThreadTimeOut(boolean value) 方法设置后,也会用于核心线程。(当然线程池中并没有对线程进行标记是否为核心线程,当数量超过核心线程数时,任意线程只有空闲时间超过设定值就会被销毁)。
当设置为0时,直接销毁非核心线程
unit,时间单位
这个参数与keepAliveTime是一起的,设定的就是超时时间的时间单位,秒、分、时等。

workQueue,阻塞队列
当核心线程池没有空闲时,将把任务任务加入阻塞队列。这个参数对线程池执行策略有很大影响。
有界队列:队列长度有上限,当核心线程已满时,新任务加入阻塞队列,阻塞队列已满时将根据maximumPoolSize判断是否创建新的线程加入线程池,当达到最大值时,将根据拒绝策略处理后续的任务。
无界队列:队列没有上限,也就是意味着队列永远不会达到上限,也就是说永远不会创建非核心线程,任务的处理速度有限。同时新来任务可以无限的向队列中添加。(警惕任务队列无限堆积的风险)

名称 是否有界 结构 默认值长度 最大值 备注
ArrayBlockingQueue 有界 数组 初始化指定大小
LinkedBlockingQueue 有界 链表 Integer.MAX_VALUE Integer.MAX_VALUE 出队和入队使用两把锁来实现
PriorityBlockingQueue 无界 数组,堆 11 Integer.MAX_VALUE-8 支持优先级但不能保证同优先级元素的顺序
DelayQueue 无界 PriorityQueue 11 创建元素时可以指定多久才能从队列中获取当前元素
LinkedTransferQueue 无界 链表 CAS实现
LinkedBlockingDeque 无界 链表 双向阻塞队列
SynchronousQueue 队列(公平),栈(非公平) 0 内部没有任何存放元素的能力 CAS实现,对本人这有点像生成者消费者中锁的通信

threadFactory,线程工厂
指定线程池中线程的创建方式,用于实现生成线程的方式、定义线程名格式、是否后台执行等等,可以用 Executors.defaultThreadFactory() 默认的实现即可,也可以用 Guava 等三方库提供的方法实现,如果有特殊要求的话可以自己定义。它最重要的地方应该就是定义线程名称的格式,便于排查问题了吧。(可以把业务请求编号等信息作为线程名称)。

handler,拒绝策略
当没有空闲的线程(线程池达到最大值maximumPoolSize)处理任务,并且等待队列已满(当然这只对有界队列有效),再有新任务进来的话,就要做一些取舍了,而这个参数就是指定取舍策略的,有下面四种策略可以选择:

ThreadPoolExecutor.AbortPolicy//直接抛出异常,这是默认策略; 
ThreadPoolExecutor.DiscardPolicy//直接丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy//丢弃队列最前面的任务,然后将新来的任务加入等待队列
ThreadPoolExecutor.CallerRunsPolicy//由线程池所在的线程处理该任务,比如在 main 函数中创建线程池,如果执行此策略,将有 main 线程来执行该任务

现有方法创建线程池(不提倡):
虽然并不提倡用 Executors 中的方法来创建线程池,但还是用他们来讲一下几种线程池的原理。
先看一下一部分源码(有部分重载方法没有展示,有threadFactory的参数重载方法):
newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

newScheduledThreadPool
继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。关于周期调度的暂不做展开。

以上几种总结一下:

方法 corePoolSize maximumPoolSize keepAliveTime unit workQueue threadFactory handler
newFixedThreadPool nThreads nThreads 0L TimeUnit.MILLISECONDS LinkedBlockingQueue threadFactory
newSingleThreadExecutor 1 1 0L TimeUnit.MILLISECONDS LinkedBlockingQueue threadFactory
newCachedThreadPool 0 Integer.MAX_VALUE 60L TimeUnit.SECONDS SynchronousQueue threadFactory

以上由ThreadPoolExecutor实现,只是参数有所不同。
不推荐使用的原因:

  • newFixedThreadPool
    由于使用默认使用的是 LinkedBlockingQueue 作为等待队列,这是一个无界队列,这也是使用它的风险所在,除非你能保证提交的任务不会无节制的增长,否则不要使用无界队列,这样有可能造成等待队列无限增加,造成 OOM。
  • newSingleThreadExecutor
    与newFixedThreadPool类似,只是设定了线程池大小为1,理由同上。
  • newCachedThreadPool
    关键点就在于它使用 SynchronousQueue 作为等待队列,它不会保留任务,新任务进来后,直接创建临时线程处理,这样一来,也就容易造成无限制的创建线程,造成 OOM
  • newScheduledThreadPool
    计划型线程池,可以设置固定时间的延时或者定期执行任务,同样是看线程池中有没有空闲线程,如果有,直接拿来使用,如果没有,则新建线程加入池。使用的是 DelayedWorkQueue 作为等待队列,这中类型的队列会保证只有到了指定的延时时间,才会执行任务
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章