深入浅出线程池
2024-09-29
13k
前言
鄙人在公司实习时,看见了公司的自定义线程池结合CompletableFuture异步获取结果,对线程池的实现感兴趣,于是花了些时间来解决来弄清楚线程池的执行原理
本文将会探讨
Executor、Executors、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、ForkJoinPool之间的关系
先简单介绍一下之间的关系,Executor是最底层的定义,ExecutorService继承了Executor
AbstractExecutorService是ExecutorService的默认实现,而ThreadPoolExecutor和ForkPool都是AbstrackExecutor的子类
Executor ——> 一切的根源
Executor接口是JUC提供的一个接口,通常用于提交Runnable任务,用于显示替代创建线程。
ExecutorService ——> 线程池管理接口
ExecutorService 是 Java 中用于管理和控制线程池的接口。它提供了管理线程生命周期的方法,并允许提交任务以供执行。换句话说,Executor中只定义了一个execute方法,管理线程池的方法全都是在这个接口中定义的
使用示例
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} return "Hello, World!";
}, executor);
future.thenAccept(result -> {
System.out.println("Result: " + result);
});
executor.shutdown();
}
本文仅仅探究线程池的使用,关于CompletableFuture的部分,鄙人还有另外一篇源码解析的文章,欢迎观看!
这里简单提一下,CompletableFuture实际上是使用我们传进去的线程池参数来执行任务的,会调用线程池Executor中定义的execute方法,而这个地方这个方法的实现者是ThreadPoolExecutor,后文会详细分析,这里先简单介绍。
源码解析
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
可以发现,ExecutorService定义了一系列线程池管理方法
AbstractExecutorService ——> ExecutorService 接口的默认实现
源码
public abstract class AbstractExecutorService implements ExecutorService {
public AbstractExecutorService() {}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
}
private static <T> void cancelAll(ArrayList<Future<T>> futures) {
}
private static <T> void cancelAll(ArrayList<Future<T>> futures, int j) {
}
}
源码中实现并定义了一些线程池的管理和操作,有一个地方是比较有意思的
AbstractExecutorService并没有实现execute方法,从而这个地方的调用实际上是调用的AbstractExecutorService的子类实现的方法,这中设计方法叫做模板方法模式
模板方法模式
由父类定义一个算法的骨架,将一些步骤实现延迟到子类中,子类可以重写这些步骤来实特定的行为,但是算法的整体逻辑由父类来设计
回到上面的示例代码中
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} return "Hello, World!";
}, executor);
当我们进入到Executors中,我们就能找到真相
原来如此,execute实际调用的是ThreadPoolExecutor中的实现!
ThreadPoolExecutor ——> 灵活的方式来创建和管理线程池
使用示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadPoolExecutor.AbortPolicy()
);
executor.execute(task);
executor.shutdown();
核心参数
参数 | 意义 |
---|---|
corePoolSize | 线程池中的核心线程数 |
workQueue | 存放提交的task |
maximumPoolSize | 线程池中允许的最大线程数 |
threadFactory | 线程工厂, 用来创建线程, 由Executors#defaultThreadFactory 实现 |
keepAliveTime | 空闲线程存活时间(默认是临时线程, 也可设置为核心线程) |
unit | 空闲线程存活时间单位枚举 |
关于线程创建后任务会如何执行
- 线程数小于核心线程数时,直接创建线程来执行任务
- 大于核心线程且工作队列没满就将任务提交给工作队列
- 大于核心线程数且队列已满,直接创建一个临时线程来处理任务
- 大于最大线程数,工作队列已满,执行拒绝策略
源码解析
当我们进入到这个类中,会发现有好多代码,这里就不贴出来了,我们只挑重点的来解析,我们来顺着上面的示例代码来深入
定义和属性
public class ThreadPoolExecutor extends AbstractExecutorService {
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
private final BlockingQueue<Runnable> workQueue;
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet<Worker> workers = new HashSet<>();
private final Condition termination = mainLock.newCondition();
private int largestPoolSize;
private long completedTaskCount;
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private volatile long keepAliveTime;
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
}
我们点进去new ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
从这里我们可以看见有一个参数是Executors中的工厂方法,这里我们稍后再提
顺着this去翻找
发现这里仅仅进行了一些赋值,emm,线索好像断了。没关系,还记得我们上文提到的execute方法吗?ThreadPoolExecutor中需要实现这个方法,让我们去看看吧
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
这里就是我们上文提到的提交任务时会遇到的情况:
- 运行中的线程数 < 核心线程数,就尝试添加一个新的工作线程来执行任务
- 大于核心线程数,就尝试将任务加到工作队列中
- 如果无法加入队列就尝试添加一个新的工作线程
- 再失败就拒绝任务
接着来看addWorker
我们到这里就很清楚了,所谓的线程池不过是管理的一堆线程的工具,实际上就是帮助我们判断合适去新增线程何时复用线程。private boolean addWorker(Runnable firstTask, boolean core) { retry: for (int c = ctl.get();;) { if (runStateAtLeast(c, SHUTDOWN) && (runStateAtLeast(c, STOP) || firstTask != null || workQueue.isEmpty())) return false; for (;;) { if (workerCountOf(c) >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); if (runStateAtLeast(c, SHUTDOWN)) continue retry; } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { int c = ctl.get(); if (isRunning(c) || (runStateLessThan(c, STOP) && firstTask == null)) { if (t.getState() != Thread.State.NEW) throw new IllegalThreadStateException(); workers.add(w); workerAdded = true; int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (!workerStarted) addWorkerFailed(w); } return workerStarted; }
最后让我们去看看线程复用是如何实现的!
线程复用的奥秘
ThreadPoolExecutor中有一个静态内部类Worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
@SuppressWarnings("serial")
final Thread thread;
@SuppressWarnings("serial")
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() {
acquire(1);
}
public boolean tryLock() {
return tryAcquire(1);
}
public void unlock() {
release(1);
}
public boolean isLocked() {
return isHeldExclusively();
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
Worker是定义了一个线程和要执行的初始任务,而线程复用要关注到这行
跳转之后,发现是调用的ThreadPoolExecutor中定义的runWorker方法
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
发现这里的逻辑:
- 获取当前线程、获取Worker中的初始任务并清空,解锁Worker允许终端
- 只要任务不为null或者从getTask中获取到新任务就一直循环执行,从而达到线程复用
- 检测线程池状态,进行一些钩子方法
到这里我们才真正找到线程池的奥秘,原来是通过循环来不断从任务队列中获取任务执行,而不是执行完任务之后就终止线程!!!
ForkJoinPool ——> 递归拆分!让并行嗨起来!并行流的原理!
篇幅有限简单介绍一下
主要组件
- ForkJoinTask:任务的基本单位,提供fork、join,用于任务的分解和合并
- ForkJoinWorkerThread:执行ForkJoinTask的工作线程
- WorkQueue:工作线程用于存储任务
- ForkJoinPool:管理和调度ForkJoinTask的执行
核心原理
工作窃取法:
- 工作窃取:当一个工作线程完成了自己的任务队列中的任务后,它会尝试从其他线程的队列中窃取任务来执行。
- 双端队列:每个工作线程的任务队列是一个双端队列,线程从队列的一端(通常是头部)获取任务执行,而其他线程从队列的另一端(通常是尾部)窃取任务。
并行流
- 使用stream()方法将数据源转换为顺序流,或使用parallelStream()方法将数据源转换为并行流。
- 使用中间操作(如filter、map、sorted等)和终端操作(如forEach、collect、reduce等)定义流的处理逻辑。
- 执行并行处理:
- 并行流会自动使用ForkJoinPool来并行处理数据。每个操作会被拆分成多个子任务,并由ForkJoinPool中的工作线程并行执行。
常见八股
为什么使用抽象类来实现接口
- 因为普通类去实现接口中的方法,子类在继承的时候必须重写父类中的方法不利于代码的复用
- 抽象类可以提供一些默认实现,从而使得子类直接使用即可
- 可以强制子类去实现某个方法(abstract)