This document shows the basic usage of the javaflow API.
First, consider the following program:
class MyRunnable implements Runnable { public void run() { System.out.println("started!"); for( int i=0; i<10; i++ ) echo(i); } private void echo(int x) { System.out.println(x); Continuation.suspend(); } } Continuation c = Continuation.startWith(new MyRunnable()); System.out.println("returned a continuation");
When the startWith method is invoked, Javaflow sets up the "environment", then invoke the run method of the object it received. It's not very important for users to know what this environment is, but that is what enables all the magics we'll see in this document.
As a result of this, you'll see the "started!" message printed in the console. The thread then goes into a for loop, calls the echo method, prints "0", then calls the Continuation.suspend() .
This is where an interesting thing happens. In this method, the stack frames that are leading up to the Continuation.suspend() and all local variables are captured into a Continuation object, and then the execution resumes by returning from the startWith method (instead of returning from the suspend method.) So the next message you'll see on the console is "returned a continuation". This all happens by using just one thread.
You can then do something else, and eventually you'll do the following:
Continuation d = Continuation.continueWith(c); System.out.println("returned another continuation");
When the continueWith method is invoked, javaflow sets up the environment again, and restores stack frames and local variables. Instead of returning from the continueWith method, the execution resumes by returning from the suspend method that never returned before.
Now what happens? The echo method returns, then you'll go another iteration of the for loop. So the next message you'll see is "1". Then, the suspend method is called again.
At this point, the stack frames and the local variables are captured into a new Continuation object, and then the execution resumes by returning from the continueWith method. So the next message you'll see is "returned another continuation".
If you think of two threads, the execution flow so far would be probably easier to understand, although with javaflow all of this happens in one thread. We can repeatedly continue the returned Continuation object so that it will print 2,3,...,9 as shown in the following code:
while(d!=null) { d = Continuation.continueWith(d); }
Eventually, the for loop exits and the run method returns. At that point, there's nothing left to execute. So the continueWith method returns null .
Now, so far the things we did can be easily done if you are to use two threads. So let's do something more interesting. Remember the 'c' object we captured earlier? We've already continued it once, but we can do it again:
Continuation.continueWith(c);
This restores the stack frames and local variables captured in 'c'. Then the execution resumes by returning from the suspend method. When 'c' was captured, the value of 'i' was 0. So the next number you'll see printed is "1". Then it executes suspend method, then the execution returns from the continueWith method.
Isn't this interesting? In a way, we went back the time and re-run the same code again. The continueWith method doesn't have to be invoked from the same method.
A Continuation can be serialized if all objects it captured is also serializable. In other words, all the local variables (including all this objects) need to be marked as Serializable . In this example, you need to mark the MyRunnable class as Serializable . A serialized continuation can be sent over to another machine or used later.
For these to work, javaflow needs to enhance the byte code of your program that runs inside the continuation-enabled environment. When the Continuation.suspend runs, all the methods on the stack frames (down to Continuation.startWith or Continuation.continueWith ) need to be enhanced.
There are two ways to instrument bytecode. One way is to do it statically. This means using the javaflow Ant task as a part of your build process, to enhance the classes that run inside continuation-enabled environment. Since the byte-code enhancement increases the class file size and slow down the execution, you might want to consider avoiding unnecessary class files enhancement.
Alternatively, you can do this dynamically at runtime, by using javaflow's ContinuationClassLoader . This works like a URLClassLoader with the byte-code enhancement. To use this, you need to separate your application into two parts; one for classes that don't need enhancement, and the other that do need enhancement. You can then configure the first portion to be loaded by the system class loader, and then load the second portion by a ContinuationClassLoader . The following code shows how to do this:
// this class lives in the system class loader public class Foo { public static void main(String[] args) { ClassLoader cl = new ContinuationClassLoader( new URL[]{new URL("latter.jar")}, Foo.class.getClassLoader()); // parent class loader cl.loadClass(...); } }