注:部分图片来自网络,如侵必删!
线程池技术
具有定时任务的线程池:ScheduledThreadPoolExecutor
普通的线程池:ThreadPoolExecutor
并发编程三要素
- 原子性:不可分割的操作,多个步骤要保证同时成功或者同时失败(synchronized)
- 有序性:程序执行的顺序和代码的顺序要保持一致(volatile)
- 可用性:一个线程对共享变量的修改,另一个线程能立马看到(volatile)
ThreadPoolExecutor 线程池
构造方法
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;
}
线程类型
核心线程、救急线程(都是懒惰性初始化)
maximumPoolSize:最大线程数
corePoolSize:核心线程数
maximumPoolSize - corePoolSize:救急线程数,所谓救急线程就是指,核心线程和阻塞队列都无法处理过多的任务时,线程池就会去创建救急线程来帮忙,如果在keepAliveTime的时间内没有任务到来,就自动销毁
例如:下图中,核心线程和阻塞队列都满了,此时如果添加了新的任务5,线程池就会创建一个救急线程去处理任务5,处理完任务5之后,在keepAliveTime的时间内没有新的任务到来,线程池就会销毁该线程
线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量
状态名 | 高 3 位 | 接收新任务 | 处理阻塞嘟列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | yes | yes | |
SHUTDOWN | 000 | no | no | 不会接收新任务,但会处理阻塞队列的剩余任务 |
STOP | 001 | no | no | 会中断正在执行的任务,并抛弃阻塞队列的任务 |
TIDYING | 010 | - | - | 任务全执行完毕,活动线程为 0 即将进入终结 |
TERMINATED | 011 | - | - | 终结状态 |
ScheduledThreadPoolExecutor 任务调度线程池
构造方法
从构造方法中,我们不难看出:ScheduledThreadPoolExecutor 和 ThreadPoolExecutor 的区别。
-
最大线程数为 Integer.MAX_VALUE,意味着救急线程可以无限创建
-
使用延迟队列 DelayedWorkQueue,这是实现 ScheduledThreadPoolExecutor 任务调度线程池最核心的地方
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
核心方法
方法名 | 概念 |
---|---|
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) | 让任务延迟 delay unit 时间后执行,Runnable 表示无返回值 |
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) | 让任务延迟 delay unit 时间后执行,Callable 表示有返回值 |
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 让任务延时 initialDelay 后以固定的速率 period 执行任务 |
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) | 让任务固定延时 initialDelay 后以固定的速率 period 执行任务 |
Executors 线程池工厂
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
基于 ThreadPoolExecutor 的构造方法我们可以知道:
- 核心线程数 == 最大线程数,即没有救急线程,故也不需要设置超时时间
- LinkedBlockingQueue 是阻塞队列的 链表 实现,因此可以认为阻塞队列是无界的,可以放任意数量的任务
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
基于 ThreadPoolExecutor 的构造方法我们可以知道:
- 核心线程数是 0,最大线程数是 Integer.MAX_VALUE,即线程池创建出来的都是救急线程,而救急线程的空闲生存时间是 60s,意味着救急线程可以无限创建
- SynchronousQueue 实现特点是:它没有容量,它的 put 和 take 都是阻塞方法,类似于 管道
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
由 FinalizableDelegatedExecutorService 可知,在方法里包装了真正的线程池,也就是我们无法直接修改核心线程数、最大线程数等参数
具体使用场景:
希望多个任务排队顺序执行,线程数固定为 1,任务数多于 1 时,会放入无界队列排序;任务执行完毕,这唯一的线程也不会被释放。
既然 singleThreadExecutor 是单线程,那为什么不使用我们自己的单线程呢?
-
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任务补救措施,但是线程池会新建一个线程保证池的正常执行
-
Executors.newSingleThreadExecutor() 线程个数始终为 1,不能修改
FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
-
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改正确处理执行任务异常
对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
RejectedExecutionHandle 拒绝策略
附上 JDK 的源码:
package java.util.concurrent;
/**
* A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
*
* @since 1.5
* @author Doug Lea
*/
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
JDK 提供了四种默认的拒绝策略实现:
public class ThreadPoolExectuor {
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
/**
* 让调用者自己去执行本任务
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
/**
* 默认策略,抛出 RejectedExecutionException 异常
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
/**
* 空实现,即放弃本次任务
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
/**
* 放弃阻塞队列中最早的任务,并用本任务代之
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
}
总结
- AbortPolicy:默认策略,抛出 RejectedExecutionException 异常
- CallerRunsPolicy:让调用让调用者自己去执行本任务
- DiscardOldestPolicy:放弃阻塞队列中最早的任务,并用本任务代之
- DiscardPolicy:空实现,即放弃本次任务
BlockingQueue 阻塞队列
常用的阻塞队列:
- ArrayBlockingQueue:基于数组的阻塞队列
- LinkedBlockingQueue:基于链表的阻塞队列
- SynchronousQueue:没有容量,它的 put 和 take 都是阻塞方法,类似于 **管道
- DelayQueue:延时队列
线程池异常处理
测试
如果不处理异常,那么这个业务异常将会被吞噬,这会给我们的业务带来难以估计的后果
package jucexception;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
public class Test {
public static final Logger LOG = Logger.getLogger("Test");
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
LOG.info("start");
int i = 1 / 0;
LOG.info("end");
});
}
}
可见,发生了异常线程结束了,但是没有异常信息打印出来,并且业务逻辑也没有正常执行!
方法一:调用者主动捕获异常
package jucexception;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
public class Test {
public static final Logger LOG = Logger.getLogger("Test");
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1);
pool.submit(() -> {
LOG.info("start");
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
LOG.info("end");
});
}
}
方法二:基于 Future
package jucexception;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;
public class Test {
public static final Logger LOG = Logger.getLogger("Test");
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> future = pool.submit(() -> {
LOG.info("start");
int i = 1 / 0;
LOG.info("end");
return true;
});
System.out.println(future.get());
}
}