参考文章:Java Executors类的9种创建线程池的方法及应用场景分析 - 威哥爱编程 - SegmentFault 思否
本文将介绍Executors中常见一些工厂方法。Executors中的工厂方法其实就是对ThreadPoolExecutor的封装,方便使用。
线程池的执行流程
- 创建线程池,并用线程工厂给线程起个好名字。
- 有任务过来,线程池才会创建线程,并执行任务。
- 后续还有任务过来,所有核心线程没有空闲的,那么就放入任务队列中等待,直到有空闲的线程。
- 任务队列如果是有界的,如果任务超过了队列的大小,此时就会创建临时线程来执行任务。
- 如果临时线程也到达了最大值,并且都没有空闲的。此时就会执行四种拒绝策略
- AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
- CallerRunsPolicy 让调用者运行任务
- DiscardPolicy 放弃本次任务
- DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
- 当任务都被完成后,临时线程到达指定的最大空闲时间后就会被释放。
可以看到ThreadPoolExecutor的最全构造方法
java
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
提交任务
这里提交任务都是线程池中提交任务的方法。
java
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
处理异常
线程池中的异常处理可以在线程执行内部捕捉,还可以用Futrue,在拿到结果后处理。
主动捕捉
java
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> {
try {
System.out.println("hello");
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
});
Futrue捕捉
java
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
System.out.println("hello");
int i = 1 / 0;
System.out.println("world");
return true;
});
try {
f.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
newFixedThreadPool
创建固定大小的线程池。
这里核心线程数==最大线程数,也就是说不会创建临时线程,所以也不需要超时时间。
工作队列是LinkedBlockingQueue,可以看成是无界的(实际上是int的最大值)可以看到下面的源码。
java
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}, initially containing the elements of the
* given collection,
* added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any
* of its elements are null
*/
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
该线程池适合运用于任务量已知,相对来说比较耗时的工作。因为它可以保证任务以固定的线程数量并行执行,同时避免了线程数量的无限制增长。然而,由于线程池的大小是固定的,如果任务提交的速率超过了线程池的处理能力,可能会导致任务在队列中等待较长时间。因此,在使用 newFixedThreadPool 时,需要根据任务的特性和预期的负载来合理设置 nThreads 的值。
newCachedThreadPool
创建临时线程干活。
核心线程数是0,最大线程数是int的最大值 --> 临时线程数是int的最大值
临时线程最大的空闲时间是60秒。
工作队列是SynchronousQueue,其特点是没有容量,没有线程来取任务是放不进取任务的。
java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这种设计使得 newCachedThreadPool 非常适合处理大量短生命周期的任务,因为它可以动态地调整线程数量以适应任务负载的变化。然而,由于它可以创建无限多的线程,如果没有适当的任务队列来控制任务的数量,可能会导致资源耗尽。因此,在使用 newCachedThreadPool 时,需要谨慎考虑任务的特性和系统的资源限制。
适用于执行大量短期异步任务,尤其是任务执行时间不确定的情况。例如,Web服务器处理大量并发请求,或者异步日志记录。
newSingleThreadExecutor
创建一个固定线程为1的线程池。
希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
java
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
与单独创建一个线程执行任务的区别:
自己创建一个单线程串行执行任务,如果任务执行失败而终止 那么没有任何补救措施 ,而线程池还 会新建一 个线程,保证池的正常工作。
与Executors.newFixedThreadPool(1)的区别:
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改。FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改。FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法