final int doExec() {
int s; boolean completed;
if ((s = status) >= 0) {
try {
completed = exec();
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
if (completed)
s = setCompletion(NORMAL);
}
return s;
}
也即是,现有ForkJoinTask的子类exec方法,均是返回true,而只有CountedCompleter返回false,所以其需要设置正常结束状态,任务才会被结算成执行完毕,在任务fork等调用时,才会结束阻塞;如果你只是往里面添加一个任务这个则不处理也没有关系
类似RecursiveAction的效果
class Task(private val num : Int,private val end : Int, completer: Task? = null) : CountedCompleter(completer) {
override fun compute() {
if (end == num) {
if (end % 2 == 0) println(“odd $end”)
propagateCompletion()
return
}
addToPendingCount(1)
val middle = (num + end) / 2
Task(num, middle, this).fork()
Task(middle + 1, end,this).fork()
}
}
类似RecursiveTask的效果
class Task(val num : Int,val end : Int, completer: Task? = null) : CountedCompleter(completer) {
@Volatile public var mResult = 0
private var t1 : Task? = null
private var t2 : Task? = null
override fun compute() {
if (end == num) {
mResult = end
tryComplete()
return
}
addToPendingCount(1)
val middle = (num + end) / 2
t1 = Task(num, middle, this).fork() as Task
t2 = Task(middle + 1, end,this).fork() as Task
}
override fun onCompletion(caller: CountedCompleter<*>?) {
if (this != caller && caller is Task) {
mResult = (t1?.mResult ?: 0) + (t2?.mResult ?: 0)
}
}
override fun getRawResult(): Int {
return mResult
}
override fun setRawResult(t: Int?) {
mResult = t ?: 0
}
}
如果不通过根任务的join等方法获取结果,而是其它数据交流的办法(Rxjava 中发射、LiveData等),则可以不重写get/setRawResult方法
某个特殊结果寻找
class Task(val num : Int,val end : Int, completer: Task? = null) : CountedCompleter(completer) {
@Volatile public var mResult = 0
override fun compute() {
if (end % 7 == 0 && end % 5 == 0) {
(root as Task).mResult = end
quietlyCompleteRoot()
return
} else if (num == end) {
return
}
addToPendingCount(1)
val middle = (num + end) / 2
Task(num, middle, this).fork()
Task(middle + 1, end,this).fork()
}
override fun getRawResult(): Int {
return mResult
}
override fun setRawResult(t: Int?) {
mResult = t ?: 0
}
}
可能还有其它场景,但是这些场景的处理都是依据pending值和其引用来确定是否设置结束状态;
- 原子操作设置值:addToPendingCount、compareAndSetPendingCount等方法
- 利用设置状态方法来处理:propagateCompletion、tryComplete、quietlyCompleteRoot等
3 原理实现
ForkJoinPool线程池,其执行任务的线程对象是ForkJoinWorkerThread子类,任务均被包装为ForkJoinTask的子类
3.1 ForkJoinWorkerThread类
Thread子类,其中主要内容有:线程队列创建、销毁、执行
3.1.1 线程队列创建
在构造器中通过ForkJoinPool.registerWorker方法为当前线程关联队列,队列位置为线程池队列数组的奇数位置
3.1.2 线程的销毁
通过ForkJoinPool.deregisterWorker方法进行销毁
3.1.4 线程的运行
run方法内为其主要逻辑,不贴代码了;需要在其线程队列建立后,持有数据还未申请空间之前进行线程执行,否则不做任何处理
回调方法onStart,表示线程开始执行;通过ForkJoinPool.runWorker方法来执行任务;onTermination回调方法接收异常处理;
3.2 ForkJoinTask类
抽象类,实现了Future、Serializable接口;其主要内容:任务异常收集、fork-join执行流程(join也可以是invoke、get等操作,但这里就依据join来讲解)
task有以下几种状态
volatile int status;
static final int DONE_MASK = 0xf0000000;
static final int NORMAL = 0xf0000000;
static final int CANCELLED = 0xc0000000;
static final int EXCEPTIONAL = 0x80000000;
static final int SIGNAL = 0x00010000;
static final int SMASK = 0x0000ffff;
- NORMAL:结束状态,正常结束,负数
- CANCELLED:结束状态,用户取消,负数
- EXCEPTIONAL:结束状态,执行异常,负数
- SIGNAL:等待通知执行状态,正数
- 0 : 起始状态
3.2.1 异常收集
异常数据收集,是根据弱引用机制来处理;弱引用任务节点结构如下:
static final class ExceptionNode extends WeakReference<ForkJoinTask<?>> {
final Throwable ex;
ExceptionNode next;
final long thrower;
final int hashCode;
ExceptionNode(ForkJoinTask<?> task, Throwable ex, ExceptionNode next,
ReferenceQueue exceptionTableRefQueue) {
super(task, exceptionTableRefQueue);
this.ex = ex; // 原始异常
this.next = next; // 相同hash的节点指针域
this.thrower = Thread.currentThread().getId(); // 线程标识
this.hashCode = System.identityHashCode(task); // 与对象地址相对应的hash
}
}
弱引用节点相关数据结构
private static final ExceptionNode[] exceptionTable; // 异常数据
private static final ReentrantLock exceptionTableLock; // 异常节点锁
private static final ReferenceQueue exceptionTableRefQueue; // 弱引用回收队列
采用的数组存储,并利用hash进行映射,单链表进行冲突解决;并在需要处理异常时,实时去除已经销毁的task节点异常;常用操作如下:
- 记录异常:recordExceptionalCompletion方法,在任务未完成的情况才会记录
- 清除当前节点异常:clearExceptionalCompletion方法
- 获取异常:getThrowableException,非当前线程异常,需要进行包装转换
- 清理无效task相关联异常:expungeStaleExceptions静态方法,清除掉回收队列中task所有相关异常节点
3.2.2 fork-join逻辑
fork方法用于向队列中保存任务;偶数任务队列中未依赖于线程,奇数队列为线程私有
public final ForkJoinTask fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
- 当前在ForkJoinWorkerThread线程中执行,则调用workQueue.push方法存入队列
- 放入线程池中队列数组中偶数位置的队列中
join方法用于阻塞获取结果
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
return (s = status) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
externalAwaitDone();
}
同样需要根据线程类型判断
- 状态小于0,也即任务已结束,则直接返回,如果是异常则会抛出异常
- 未执行时,不是ForkJoinWorkerThread线程内执行,以当前任务实例为锁对象,进行等待(更具体的逻辑在externalAwaitDone方法内分析)
- 未执行时,ForkJoinWorkerThread线程内执行;如果任务为当前线程队列的顶部(也就是最后一个提交的)且执行后处于结束状态,则返回
- 线程池内awaitJoin进行等待(其时可能存在窃取其它任务队列进行执行)
externalAwaitDone方法
首先尝试执行,如果满足下面条件,则会执行doExec方法(调用exec()方法进行具体执行)
- CountedCompleter任务类型,则common线程池方法externalHelpComplete返回true
- 其它任务类型,common线程池tryExternalUnpush方法返回true
如果未执行,则通过staus原子操作+synchronized锁,进行等待
3.2 ForkJoinPool类
这里主要有一些常量的意义、队列结构、执行流程、窃取线程思路;
3.2.1 状态成员变量
volatile long ctl;
volatile int runState;
final int config;
ct1,64位,分为4段,每相邻16位为一段
- 高16位,正在处理任务的线程个数;初始化为并行数的负值(构造器中线程的并行线程数,一般来说为能创建的最大线程数)
- 次高16位,线程总数,初始化为并行数的负值
- 次低16位,线程状态,小于0时需要添加新的线程,或者说48位的位置为1时,需要添加线程
- 低16位,空闲线程对应的任务队列在队列数组的索引位置
runState,有下面几种状态,默认态为0
private static final int STARTED = 1;
private static final int STOP = 1 << 1;
private static final int TERMINATED = 1 << 2;
private static final int SHUTDOWN = 1 << 31;
config:低16位代表 并行度(parallelism),高16位:队列模式,默认是后进先出
3.2.3 线程队列
volatile WorkQueue[] workQueues
数组结构,分为线程队列和非线程队列,随机寻找位置进行创建与查找;达到WorkQueue均匀处理,以减少WorkQueue同步开销
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
-
自行下载直达领取链接:点击这里前往GitHub
试题解析,framework源码!
[外链图片转存中…(img-lSo6jdBD-1646142287451)]
-
自行下载直达领取链接:点击这里前往GitHub