简介
continuation是服务于虚拟线程的一个类,主要提供run及yield的功能,使语言可以在任意点保存执行状态并且在之后的某个点返回。功能的主要实现都是在jvm源码中,Continuation.java 仅是流程代码。
如需使用,需添加以下指令到启动到jvm option:
--add-exports java.base/jdk.internal.vm=ALL-UNNAMED
run执行路径:
yield执行路径:
1、字段方法说明:
从结构上来看,Continuation有两个维度,一个是纵向的链表维度(存在parent及child节点),一个是横向的scope维度,相同scope的属于同一类continuation(从代码实现角度来说,scope相对于对continuation打上个tag,使得用户在需要yield时不会yield错了对象)
// unsafe实例
private static final Unsafe U = Unsafe.getUnsafe();
// 是否开启本地缓存
private static final boolean PRESERVE_EXTENT_LOCAL_CACHE;
// JavaLangAccess操作对象,主要用于对 Java 核心类库中的一些非公开方法和字段的访问
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
// 实际运行的 runnable
private final Runnable target;
/* While the native JVM code is aware that every continuation has a scope, it is, for the most part,
* oblivious to the continuation hierarchy. The only time this hierarchy is traversed in native code
* is when a hierarchy of continuations is mounted on the native stack.
*/
// scope对象,使用同一scope的Continuation可以相互之间yield
private final ContinuationScope scope;
// 父节点
private Continuation parent; // null for native stack
// 子节点
private Continuation child; // non-null when we're yielded in a child continuation
// 栈内存空间,在java类中没有赋值,赋值操作是在native方法中进行
private StackChunk tail;
// 当前Continuation是否已完成
private boolean done;
// 装载状态
private volatile boolean mounted = false;
// yield信息(可能是scope,也可能是yield的结果)
private Object yieldInfo;
//
private boolean preempted;
private Object[] extentLocalCache;
2、RUN方法
public final void run() {
while (true) {
// 装载
mount();
JLA.setExtentLocalCache(extentLocalCache);
// 已完成时再调用run则抛异常
if (done)
throw new IllegalStateException("Continuation terminated");
// 获取当前载体线程
Thread t = currentCarrierThread();
// 当parent和cild都yield时,child先于paret run时,会进入此if
if (parent != null) {
if (parent != JLA.getContinuation(t))
throw new IllegalStateException();
} else
this.parent = JLA.getContinuation(t);
JLA.setContinuation(t, this);
try {
boolean isVirtualThread = (scope == JLA.virtualThreadContinuationScope());
// 此处判断是否存在堆栈内存空间,如不存在则说明未开始
if (!isStarted()) { // is this the first run? (at this point we know !done)
// enterSpecial -> enter -> enter0 -> target.run
enterSpecial(this, false, isVirtualThread);
} else {
assert !isEmpty();
enterSpecial(this, true, isVirtualThread);
}
} finally {
// 此处为什么需要读写屏障
fence();
try {
assert isEmpty() == done : "empty: " + isEmpty() + " done: " + done + " cont: "
+ Integer.toHexString(System.identityHashCode(this));
//
JLA.setContinuation(currentCarrierThread(), this.parent);
if (parent != null)
parent.child = null;
postYieldCleanup();
unmount();
if (PRESERVE_EXTENT_LOCAL_CACHE) {
extentLocalCache = JLA.extentLocalCache();
} else {
extentLocalCache = null;
}
JLA.setExtentLocalCache(null);
} catch (Throwable e) {
e.printStackTrace();
System.exit(1);
}
}
// we're now in the parent continuation
assert yieldInfo == null || yieldInfo instanceof ContinuationScope;
// 唯一跳出循环的点
if (yieldInfo == null || yieldInfo == scope) {
this.parent = null;
this.yieldInfo = null;
return;
} else {
// 进入此代码块必要条件是yieldInfo存在且不为当前的scope,既yield的scope非自身的scope
parent.child = this;
// 此时调用 yield0 方法,将当前的continuation及需要yield的scope传递,直到匹配到yieldInfo == scope,即链表向上查找
parent.yield0((ContinuationScope) yieldInfo, this);
// 断链
parent.child = null;
}
}
}
3、Yield方法
疑问:为什么yield方法要设计成静态的方法?(此处能充分体现scope的作用,但为何如此设计)
此处yield设计成静态方法,个人认为是想要让continuation的维度在scope上控制,而不是在实例上控制。假如我们有一个Continuationd的List对象,list中存在scope=A与scope=B的continuation实例,分别执行任务a和任务b,如果我需要暂停任务a,只需要list.stream().foreach(c → Continuation.yield(scopeA));
猜想:如果把list换成线程池,把continuation换成VirtualThread,是不是就能够对scope范围的VirtualThread进行yield?
/**
* Suspends the current continuations up to the given scope
*
* @param scope The {@link ContinuationScope} to suspend
* @return {@code true} for success; {@code false} for failure
* @throws IllegalStateException if not currently in the given {@code scope},
*/
public static boolean yield(ContinuationScope scope) {
Continuation cont = JLA.getContinuation(currentCarrierThread());
Continuation c;
// 基于Continuation实例当前向父节点遍历,直到匹配虚拟线程类型的ContinuationScope的Continuation,如果没有匹配的Continuation会抛出异常中断流程
// 此处其实是在校验当前Continuation链表中是否存在需要yield的scope
for (c = cont; c != null && c.scope != scope; c = c.parent)
;
if (c == null)
throw new IllegalStateException("Not in scope " + scope);
return cont.yield0(scope, null);
}
/**
* 此方法有两个调用的地方,一个是yield,另一个是run,其中run方法中会传child
*
*/
private boolean yield0(ContinuationScope scope, Continuation child) {
preempted = false;
// 此处记录需要yield 的 scope
if (scope != this.scope)
this.yieldInfo = scope;
// 该方法由c++实现,具体:stubGenerator_aarch64.cpp → generate_cont_doYield() 方法
// 主要作用是将当前线程的执行状态保存并返回到调用者,即真正实现yield地方
int res = doYield();
U.storeFence(); // needed to prevent certain transformations by the compiler
assert scope != this.scope || yieldInfo == null
: "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res;
assert yieldInfo == null || scope == this.scope || yieldInfo instanceof Integer
: "scope: " + scope + " this.scope: " + this.scope + " yieldInfo: " + yieldInfo + " res: " + res;
// 此处代码的作用是将this的res传递给child,并将this.yieldInfo = null
if (child != null) { // TODO: ugly 作者的吐槽,想看后续会怎么优化
if (res != 0) {
child.yieldInfo = res;
} else if (yieldInfo != null) {
assert yieldInfo instanceof Integer;
child.yieldInfo = yieldInfo;
} else {
child.yieldInfo = res;
}
this.yieldInfo = null;
} else {
if (res == 0 && yieldInfo != null) {
// 此处传递yieldInfo至链表最末尾的continuation
res = (Integer) yieldInfo;
}
this.yieldInfo = null;
if (res == 0)
onContinue();
else
// 非 0 则说明 pinned ,抛异常
onPinned0(res);
}
assert yieldInfo == null;
return res == 0;
}
4、实例说明
以下代码为continuation 、child1、child2组成的链表调用,在child2进行yield(scope)操作之后,会遍历链表,将当前节点yield,直至continuation实例的scope等于目标scope为止:
public static void main(String[] args) {
ContinuationScope scope = new ContinuationScope("example1");
ContinuationScope scope2 = new ContinuationScope("example2");
ContinuationScope scope3 = new ContinuationScope("example3");
Continuation child2 = new Continuation(scope3, () -> {
System.out.println("before scope yield");
Continuation.yield(scope);
System.out.println("after scope yield");
});
Continuation child1 = new Continuation(scope2, () -> {
System.out.println("before child2 run");
child2.run();
System.out.println("after child2 run");
});
Continuation continuation = new Continuation(scope, () -> {
System.out.println("before child1 run");
child1.run();
System.out.println("after child1 run");
});
System.out.println("before run");
continuation.run();
System.out.println("before run again");
continuation.run();
System.out.println("end");
}
输出结果: