Executor框架
# Executor框架
Java中将任务和调度分开。任务单元为Runnbale,而执行机制和任务调度由Executor框架提供。
在JVM线程模型中,java.lang.Thread与系统线程是一一对应的。Executor负责若干个任务映射成对应的Java线程,操作系统底层再调度系统线程。
# 1.Executor框架简介
框架主要包含三个部分:
- Runnable接口不会返回结果,而Callable可以返回结果。
- Executor接口创建线程执行任务。
- Future接口用于获取异步计算的结果。FutureTask实现类。
Executors是工厂类,用于创建两种类型的线程池:
# 2.ThreadPoolExecutor
下面介绍如下三种线程池:
# FixedThreadPool:
- 线程数量:核心线程数、最大线程数都设置为固定值。
- 任务队列:使用无界队列,不存在拒绝任务的情况。存在任务过多内存移除的风险。
# SingleThreadExecutor
- 线程数量:核心线程数、最大线程数都设置为1.
- 任务队列:使用无界队列。反复从任务队列取出并执行,因此任务执行是有序的。
# CachedThreadPool
线程队列执行poll将空闲线程出队,如果60s内没有任务offer进来,那么该线程直接终止。
- 线程数量:核心线程数为0,最大线程数为MAX_VALUE。
- 任务队列:使用无容量的SynchronousQueue。必须等队列中的添加元素被消费后才能继续添加新的元素,否则会被阻塞。
- 适用于执行大量短期异步任务
# 3.ScheduledThreadPoolExecutor
可以延期执行任务,适用于多个后台线程执行周期任务。
每个任务对象ScheduledFutureTask主要几个成员变量:
- time:开始执行时刻
- period:任务执行的周期(过了多久要执行下一次)
任务队列使用DelayQueue,会根据任务的开始执行时刻进行优先级排序:
- 取出最早time的任务,并执行
- 修改time为下次执行的时间currentTime+period
- 放回任务队列中
# 4.FutureTask
注意:本身FutureTask并不是一个线程,需要启动一个新的线程执行。在另一个调用get()的线程才能被阻塞。
# API用例
FutureTask提供了以下异步任务方法和API:
- FutureTask本身实现了Runnable接口,因此可以执行run方法执行任务。
- get()方法会阻塞“调用线程”,知道任务执行结束并返回结果。
- cancel()方法会导致任务永远不会被执行。
使用时创建FutureTask任务,并交给线程池或创建一个新线程来执行。通过get()方法阻塞当前线程获取结果。
FutureTask<Integer> future = new FutureTask<Integer>(new Job(20));
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(future);
Integer integer = future.get();
1
2
3
4
2
3
4
# 源码解析
内部是通过AQS实现一个同步类Sync,通过sync来控制执行状态。
# get()方法
通过awaitDone()方法阻塞线程:
自旋判断当前任务执行状态state,若执行完毕则直接返回结果。否则按序执行如下步骤:
CAS向等待队列加入当前get调用的线程(创建WaitNode节点),存在多个线程都调用get阻塞的情况。
LockSupport.part阻塞当前线程
# run()方法
执行完任务后,CAS修改线程状态,并最后通过finishCompletion()方法唤醒等待队列的线程:
- CAS获取waiters等待队列,并置为空。(实际上这里不需要多线程控制)
- 依次取出每个等待队列节点,并执行LockSupport.unpark唤醒线程。
编辑 (opens new window)
上次更新: 2023/12/15, 15:49:57