📄 ch04s02.html
字号:
} switch (count) { case 0: System.out.println("Parent: 1"); s = null; case 1: try { child(1, s); } catch (RecordStackException e) { parentRecordStack(e.getRuntimeStack(), 1); } s = null; case 2: System.out.println("Parent: 2"); s = null; case 3: try { child(2, s); } catch (RecordStackException e) { parentRecordStack(e.getRuntimeStack(), 3); } s = null; case 4: System.out.println("Parent: 3"); s = null; } } static void childRecordStack(RuntimeStack s, int c, int i) { s.pushCount(c); s.pushInt(i); throw new RecordStackException(s); } static void child(int i, RuntimeStack s) { int count = 0; if (s != null) { count = s.popCount(); i = s.popInt(); } switch (count) { case 0: System.out.println("Child: i = " + i); s = null; case 1: if (i == 2) childRecordStack(new RuntimeStack(), 2, i); case 2: System.out.println("Child exits."); s = null; } } public static void main(String[] args) { try { parent(null); } catch (RecordStackException e) { System.out.println("Record stack exception caught."); RuntimeStack s = e.getRuntimeStack(); parent((RuntimeStack) s.clone()); parent((RuntimeStack) s.clone()); parent((RuntimeStack) s.clone()); } }}</pre><p>上面这段代码的输出如下。注意到Record stack exception caught之后的输出重复了三遍。</p><pre class="screen">Parent: 1Child: i = 1Child exits.Parent: 2Child: i = 2Record stack exception caught.Child exits.Parent: 3Child exits.Parent: 3Child exits.Parent: 3</pre><p>我们来看看这段奇怪的代码。我们的任务一是要能够把stack保存在一个RuntimeStack对象中,二是要能够恢复一个给定的RuntimeStack对象中的状态。注意到我们在RuntimeStack中给了count和本地变量两个不同的stack。这其实并不必要,不过至少我觉得这样比较清楚。</p><p>我们做的主要变化在DuplicateStack类的parent()和child()两个函数,这些变化具体为:</p><div class="orderedlist"><ol type="1"><li><p>两个函数都多接受一个RuntimeStack参数。函数的开头便是检查该参数,当RuntimeStack是null时,两个函数都从头执行起;否则,函数会从中获取本地变量的值(例如child()中的i),并跳到RuntimeStack中指定的count处。这些操作使函数能够恢复到RuntimeStack指明的状态。</p></li><li><p>两个函数的主体部分都变成了一个大switch,每个语句前多了一个case。这是因为当有RuntimeStack参数时我们需要能够跳到Stack指定的任何语句,Java源代码里的做法是从Stack中读取count,然后switch到count所指定的语句。如果我们在bytecode上进行操作,那么我们就不需要这个switch了,因为我们可以直接goto到想要到的地方。</p></li><li><p>每个可能使用continuation的函数调用都在一个try/catch结构中,catch会调用一个对应的XXXRecordStack函数。其操作是纪录下函数当前的状态,包括正在执行的语句编号和本地变量的值,然后重新抛出RecordStackException。这样一层一层抛下去,每个函数就都能纪录下自己的状态并放到RecordStackException的RuntimeStack中去。下面是相关的核心代码:</p><pre class="programlisting">static void childRecordStack(RuntimeStack s, int c, int i) { s.pushCount(c); s.pushInt(i); throw new RecordStackException(s);}</pre></li></ol></div><p>有了纪录状态和恢复状态的能力,我们就可以进行我们的“黑色魔法”了。我们要纪录下当前Stack时,只须调用XXXRecordStack函数纪录下本函数当前状态然后抛出RecordStackException就可以了。函数main()会截获该RecordStackException并取出其中的Stack。这时,main()函数就可以任意地复制和调用该Stack了。上面的例子调用了三遍Stack,所以我们会看到三个“Child exits. Parent: 3”。我们看到这里的RuntimeStack已经非常接近与一个完整的continuation了。</p><p>我们当然不可能像上面那样写代码,不过把普通的代码转换成上面格式的过程相当死板,所以我们可以用工具自动完成。当然,代码的大小和速度都会远不如普通的Java。</p></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch04s01.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="ch04.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="ch04s03.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">1. Continuation的常规实现方法 </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> 3. 在Java上实现Continuation:基于heap法</td></tr></table></div></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -