参考文章:
1.浅谈线程池ThreadPoolExecutor核心参数
https://www.cnblogs.com/stupid-chan/p/9991307.html
2.Java线程池 ThreadPoolExecutor(一)线程池的核心方法以及原理
https://blog.csdn.net/m0_37506254/article/details/90574038
3.Java 中的几种线程池,你之前用对了吗
https://www.cnblogs.com/fengzheng/p/9297602.html
4.线程池异常处理之重启线程处理任务
https://www.cnblogs.com/hapjin/p/10240863.html
整理下线程池的相关知识。阿里巴巴的规范是不允许使用Java提供的 Executors 返回的线程池,因为默认的线程池都存在一定的问题。本文主要从以下几个方面进行总结
1.默认线程池的问题
2.线程池的核心参数
3.线程池的相关问题
4.手动创建线程池
默认线程池的问题
如果使用 Executors 去创建线程池,使用阿里巴巴的插件会自动进行提示,
提示如下 :
说明 Java,默认提供的4种线程池创建方式都是不安全的。先看下默认的线程池创建方式的问题:
单线程线程池
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue, and uses the provided ThreadFactory to
* create a new thread when needed. Unlike the otherwise
* equivalent {@code newFixedThreadPool(1, threadFactory)} the
* returned executor is guaranteed not to be reconfigurable to use
* additional threads.
*
* @param threadFactory the factory to use when creating new
* threads
*
* @return the newly created single-threaded Executor
* @throws NullPointerException if threadFactory is null
*/
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
再向下跟踪
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default rejected execution handler.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
可以看到内部调用了线程池的核心创建方法,newSingleThreadExecutor 创建出来的单线程线程池 最主要的问题,是因为使用了 new LinkedBlockingQueue<Runnable>() 作为等待队列,该队列为无界队列,会导致堆积大量请求线程,从而导致OOM.
固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10,Executors.defaultThreadFactory());
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue, using the provided
* ThreadFactory to create new threads when needed. At any point,
* at most {@code nThreads} threads will be active processing
* tasks. If additional tasks are submitted when all threads are
* active, they will wait in the queue until a thread is
* available. If any thread terminates due to a failure during
* execution prior to shutdown, a new one will take its place if
* needed to execute subsequent tasks. The threads in the pool will
* exist until it is explicitly {@link ExecutorService#shutdown
* shutdown}.
*
* @param nThreads the number of threads in the pool
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
* @throws NullPointerException if threadFactory is null
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
也和单线程线程池一样的问题, 是因为使用了 new LinkedBlockingQueue<Runnable>() 作为等待队列,该队列为无界队列,会导致堆积大量请求线程,从而导致OOM.
缓存型线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
向下跟踪
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available, and uses the provided
* ThreadFactory to create new threads when needed.
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
* @throws NullPointerException if threadFactory is null
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
线程池的最大线程大小 max 为 Integer 上限,会创建大量的等待线程,从而引发OOM
延迟执行线程池
public void scheduleThreadPool() throws Exception{ ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10, Executors.defaultThreadFactory()); scheduledExecutorService.schedule(new Runnable() { @Override public void run() { System.out.println("666" + new Date()); } }, 4, TimeUnit.SECONDS); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println("777" + new Date()); } }, 1, 4, TimeUnit.SECONDS); Thread.sleep(1000 * 60); scheduledExecutorService.shutdown(); }
向下跟踪代码:
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* @param threadFactory the factory to use when the executor
* creates a new thread
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
* @throws NullPointerException if threadFactory is null
*/
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
跟踪 ScheduledThreadPoolExecutor(corePoolSize, threadFactory); 构造方法:
/**
* Creates a new {@code ScheduledThreadPoolExecutor} with the
* given core pool size.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
最后看到 线程池的最大线程大小 max 为 Integer 上限,会创建大量的等待线程,从而引发OOM
结论:
说明默认的4种线程池都多多少少存在问题 !!
============================
线程池的核心参数
看到上面的默认线程池都用到了 ThreadPoolExecutor 这个类,这个类也是手动创建线程的核心类
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
看下最后的最终构造函数:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池的核心参数为以下7个
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
下面一一进行解释
-
int corePoolSize
核心线程数,当有任务进来的时候,如果当前线程数还未达到 corePoolSize 个数,则创建核心线。
默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,
核心线程有几个特点:
1、当线程数未达到核心线程最大值的时候,新任务进来,即使有空闲线程,也不会复用,仍然新建核心线程;
2、核心线程一般不会被销毁,即使是空闲的状态,但是如果通过方法 allowCoreThreadTimeOut(boolean value) 设置为 true 时,超时也同样会被销毁;
3、生产环境首次初始化的时候,可以调用 prestartCoreThread() / prestartAllCoreThreads() 方法 ,来预先创建所有核心线程,避免第一次调用缓慢;
-
int maximumPoolSize
除了有核心线程外,有些策略是当核心线程占满(无空闲)的时候,还会创建一些临时的线程来处理任务,maximumPoolSize 就是核心线程 + 临时线程的最大上限。临时线程有一个超时机制,超过了设置的空闲时间没有事儿干,就会被销毁
-
long keepAliveTime
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。
但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
-
TimeUnit unit
参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
-
BlockingQueue<Runnable> workQueue
一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响。
队列分为有界队列和无界队列。
有界队列:队列的长度有上限,当核心线程满载的时候,新任务进来进入队列,当达到上限,有没有核心线程去即时取走处理,这个时候,就会创建临时线程。(警惕临时线程无限增加的风险)
无界队列:队列没有上限的,当没有核心线程空闲的时候,新来的任务可以无止境的向队列中添加,而永远也不会创建临时线程。(警惕任务队列无限堆积的风险)
除此之外,这里的阻塞队列有以下几种选择:
1、ArrayBlockingQueue:基于数组的先进先出,创建时必须指定大小,超出直接corePoolSize个任务,则加入到该队列中,只能加该queue设置的大小,其余的任务则创建线程,直到(corePoolSize+新建线程)> maximumPoolSize。
2、LinkedBlockingQueue:基于链表的先进先出,无界队列。超出直接corePoolSize个任务,则加入到该队列中,直到资源耗尽。
3、SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue 和 Synchronous。线程池的排队策略与BlockingQueue有关。
-
ThreadFactory threadFactory
它是一个接口,用于实现生成线程的方式、定义线程名格式、是否后台执行等等.
可以用 Executors.defaultThreadFactory() 默认的实现即可,
也可以用 Guava 等三方库提供的方法实现,
如果有特殊要求的话可以自己定义。它最重要的地方应该就是定义线程名称的格式,便于排查问题了吧
-
RejectedExecutionHandler handler
当没有空闲的线程处理任务,并且等待队列已满(当然这只对有界队列有效),再有新任务进来的话,就要做一些取舍了,而这个参数就是指定取舍策略的,有下面四种策略可以选择:
ThreadPoolExecutor.AbortPolicy:直接抛出异常 RejectedExecutionException ,这是默认策略;
ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后将新来的任务加入等待队列
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,并提供一种简单的反馈机制,可以有效防止新任务的提交。比如在 main 函数中提交线程,如果执行此策略,将有 main 线程来执行该任务
ThreadPoolExecutor.AbortPolicy:直接抛出异常 RejectedExecutionException ,这是默认策略; Java 提供的4种默认实现的线程池都是使用的这种策略。
线程池的相关问题
线程池相关方法
线程池也提供了一些相关的方法,大致如下:
execute()
submit()
shutdown()
shutdownNow()
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。
execute()
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()
shutdown() 提供一种有序的关机,会等待当前缓存队列任务全部执行完成才会关闭,但不会再接收新的任务(相对较优雅)。
shutdownNow()
shutdownNow() 会立即关闭线程池,会打断正在执行的任务并且会清空缓存队列中的任务,返回的是尚未执行的任务。
corePoolSize与maximumPoolSize关系
1、池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程
2、池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程
3、池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务
4、池中线程数大于大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务
手动创建线程池
下面演示下如何手动创建线程池:
这里我们使用的 Guava 的 ThreadFactory, 相关的 pom
<dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>14.0.1</version> </dependency> </dependencies>
线程池创建代码
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
ExecutorService taskExe = new ThreadPoolExecutor(1, 1, 200L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
创建了1个线程池 coreSize 1, maxSize 1, 使用有界等待队列初始大小为1, 传递Guava 创建的线程工厂(主要是为了给线程命名), 拒绝策略为直接抛出异常
测试代码
package thread.pool;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.*;
/**
* Created by szh on 2020/6/8.
*/
public class ThreadPoolManual {
public static int i = 1;
public static volatile boolean flag = false;
public static void main(String[] args) {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
ExecutorService taskExe = new ThreadPoolExecutor(1, 1, 200L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
taskExe.submit(new Thread(() -> {
while (ThreadPoolManual.i <= 99) {
if (ThreadPoolManual.flag == false) {
System.out.println(Thread.currentThread().getName() + " " + i);
ThreadPoolManual.i++;
ThreadPoolManual.flag = true;
}
}
}));
taskExe.submit(new Thread(() -> {
while (ThreadPoolManual.i <= 100) {
if (ThreadPoolManual.flag == true) {
System.out.println(Thread.currentThread().getName() + " " + i);
ThreadPoolManual.i++;
ThreadPoolManual.flag = false;
}
}
}));
taskExe.submit(new Runnable() {
@Override
public void run() {
System.out.println("xxxxx");
}
}
);
}
}
分析
总共3个线程,2个线程 交替打印 0~ 100, 格外并提交了1个线程用来干扰,
因为线程池当前运行一个线程1,另一个线程处于等待队列,第3个线程触发了拒绝策略。
输出
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@5b480cf9 rejected from java.util.concurrent.ThreadPoolExecutor@6f496d9f[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at thread.pool.ThreadPoolManual.main(ThreadPoolManual.java:42)
thread-call-runner-0 1