阿里巴巴Java手册中,关于线程池:

* 线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
* 使用线程池的好处,是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。
* 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
 

线程池的好处:

* 可以重用线程,避免线程创建的开销;
* 任务过多时,通过排队避免创建过多线程,减少系统资源消耗和竞争,确保任务有序完成。
一、JUC线程池详解

Java JUC包中的实现类是ThreadPoolExecutor,继承AbstractExecutorService,实现了ExecutorService。

ThreadPoolExecutor比较重要的两个构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long
keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) public
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其中:

* corePoolSize : 核心线程数量
* maximumPoolSize : 最大线程数量
* keepAliveTime / unit : 空闲线程存活时间
* workQueue : 任务队列
* threadFactory : 线程工厂
* handler : 拒绝策略
 

1、corePoolSize,核心线程数量,刚创建一个线程池后,不会创建任何线程。

当有新任务到来时,如果当前线程数量小于corePoolSize,会创建一个新线程执行该任务,即使其他线程是空闲的,也会创建新线程。如果线程数量大于corePoolSize,不会立即创建新线程,而是尝试排队,如果因为队列满了或其他原因不能立即入队,就不会排队,而是检查线程个数是否达到了maximumPoolSize,如果没有达到,继续创建新线程,直到线程数达到maximumPoolSize。

流程图

核心线程:当线程个数小于corePoolSize时的线程。

核心线程默认行为:

* 不会预先创建,只有当有任务时才会创建。
* 不会因为空闲而被终止,keepAliveTime参数不适用核心线程。
改变这些默认行为ThreadPoolExecutor有如下方法:
// 预先创建所有核心线程 public int prestartAllCoreThreads(); //
创建一个核心线程,如果所有核心线程都已经创建,返回false public boolean prestartCoreThread(); //
参数为true时,允许keepAliveTime适用于核心线程 public void allowCoreThreadTimeOut(boolean
value)
 

2、keepAliveTime,目的是为了释放多余的线程资源。当线程池中线程个数大于corePoolSize时额外空闲线程的存活时间。一个非核心线程,在空闲等待新任务的最长等待时间。0表示所有线程都不会超时终止。

 

3、workQueue,阻塞队列BlockingQueue:

* LinkedBlockingQueue:基于链表,可以指定最大长度,默认无界
* ArrayBlockingQueue:基于数组,有界
* PriorityBlockingQueue:基于堆,无界阻塞优先级队列
* SynchronousQueue:没有实际存储空间的同步阻塞队列。
对于无界队列,线程个数最多只能达到corePoolSize,达到corePoolSize后,新任务总会排队,maximumPoolSize就没有意义了。

对于SynchronousQueue,当尝试排队时,只有正好有空闲线程在等待接受任务时,才会入队成功,否则,总是会创建新线程,直到maximumPoolSize。

 

4、handler,RejectedExecutionHandler,任务拒绝策略

队列有限,并且maximumPoolSize有限,当队列排满,线程个数也达到maximumPoolSize,此时新任务会触发线程池的任务拒绝策略。

ThreadPoolExecutor实现了4种处理方式:

* ThreadPoolExecutor.AbortPolicy:默认方式,抛出异常
* ThreadPoolExecutor.DiscardPolicy:静默处理,忽略新任务,不抛出异常,也不执行
* ThreadPoolExecutor.DiscardOldestPolicy:将等待时间最长的任务扔掉,然后新任务入队
* ThreadPoolExecutor.CallerRunPolicy:在任务提交者线程中执行新任务,而不是交给线程池的线程执行。
拒绝策略只有在队列有界,maximumPoolSize有限的情况下才会触发。

如果队列无界,服务不了的任务总是会排队;请求队列可能会消耗非常大的内存,甚至引发OOM;

如果队列有界但maximumPoolSize无限,可能会创建过多的线程,占满CPU和内存,使得任何任务都难以完成。

在任务量非常大的场景中,需要让拒绝策略有机会执行。

 

5、threadFactory,线程工厂

ThreadPoolExecutor的默认实现是Executors类中的静态内部类DefaultThreadFactory。

创建一个线程,设置默认名称(pool-线程池编号-thread-线程编号),设置daemon属性为false,设置线程优先级为标准默认优先级(5)。

如果要自定义线程属性,可以实现自定义的ThreadFactory。

 

二、工厂类Executors

虽然不推荐直接使用Executors工厂类创建线程池,但还是要了解一下利弊。

1.newSingleThreadExecutor

只使用一个线程,使用无界队列,线程创建后不会超时,顺序执行所有任务。

适用于需要保证所有任务被顺序执行的场合。

无界队列,如果排队任务过多,可能会消耗过多的内存。
public static ExecutorService newSingleThreadExecutor() { return new
FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
 

2.newFixedThreadPool

使用固定树木的线程,使用无界队列,线程创建后不会超时终止。

无界队列,如果排队任务过多,可能会消耗过多的内存。
public static ExecutorService newFixedThreadPool(int nThreads) { return new
ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new
LinkedBlockingQueue<Runnable>()); }
 

3.newCachedThreadPool

核心线程数为0,最大线程数为Integer的最大值,线程空闲时间为60秒,队列为SynchronousQueue。

当新任务提交,正好有空闲线程在等待任务,则空闲线程接受该任务,否则总是创建新线程。对任一空闲线程60s内没有新任务则终止。
public static ExecutorService newCachedThreadPool() { return new
ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new
SynchronousQueue<Runnable>()); }
 

使用场景对比:

* 系统负载很高 - newFixedThreadPool
* newFixedThreadPool,通过队列对新任务排队,保证有足够的资源处理实际任务;
* newCachedThreadPool,为每个任务创建一个线程,导致创建过多的线程,竞争CPU和内存资源;
* 系统负载不太高,单个任务执行时间比较短 - newCachedThreadPool
* newCachedThreadPool,效率可能更高,因为任务可以不经排队,直接交给一个空闲线程或新建线程。
* 系统负载可能极高 - 两者都不是最好的选择,应根据具体情况自定义合适的参数。
* newFixedThreadPool,队列过长
* newCachedThreadPool,线程过多
* CPU密集型任务(计算型任务),一般线程数量为CPU数量的1~2倍,过多线程可能增大上下文切换的开销。
* IO密集型任务,相对比CPU密集型任务,需要多一些线程,根据具体的IO阻塞时长进行考量决定。如tomcat,默认最大线程数为200。
 

网上的帖子质量参差不齐,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

 

技术
©2019-2020 Toolsou All rights reserved,
中国月球车“月兔二号”在月球发现一块奇怪岩石Vue常用特性(一)关于过年R语言聚类分析案例这些歌,程序员千万万万万别听!崮德好文连载 - 活该你是工程师(自序)20考研吉大计算机学院软件学院人工智能学院考研高分学长复习攻略!!!中国最长高铁正式开通!跑完全程最快30.5小时中台透彻讲解过拟合和欠拟合的形象解释