Updated March 30, 2023
Introduction to Java Coroutines
Java coroutines are defined as a set of instructions that are sent to the computer by packaging it as a unit for generalizing a specific task to be performed by a style of multi-tasking where there is no switch from a running process to the other process and instead runs it concurrently as a co-operative multi-tasking. These instructions are executed by allowing them to be suspended and resumed. We are quite familiar with the genre of programs written through co-routines, cooperative tasks, event loops, iterators, pipes, etc. However, one typically gets confused about the concept of coroutines with respect to the other concepts like subroutines, threads, generators, mutual recursion.
Syntax of Java Coroutines
Given below is the syntax mentioned:
1. Entering dependencies in POM.
<dependency>
<groupId> {package name goes here} </groupId>
<artifactId> {artifact ID goes here} </artifactId>
<version> {version number goes here} </version>
</dependency>
2. Declaring the class instance of the coroutine.
public static final class < Class name goes here > implements Coroutine
3. Override implementations on a parent class.
@Override
4. Declaring Coroutines through com.offbynull.coroutines.
CoroutineRunnerr = new CoroutineRunner(new <class name> ());
How do Coroutines Works in Java?
Before we see about the coroutines in Java, let us look at the different ways of implementing the same as the different ways will determine the working of the coroutines in pure Java. There are, in fact, some restrictions that are imposed by the Java’s abstractions, but the implementation doesn’t stop from getting implemented.
Widely accepted, there are 4 methods which take care of the implementation of coroutines.
- In order to support coroutines natively, there are modified JVM that are patched to take care about the implementation of coroutines. One of such modified JVM is the Da Vinci Machine Project.
- By rewriting the regular bytecode, one can also implement coroutine functionality. These routines are taken care of either on the fly or at the compile time. There are various implementations present viz. from offbynull, Google code archive and/or Apache commons.
- One can also implement Java Native Interface through platform-specific mechanisms and implemented in the OS or C libraries and help provide the JVM functionality.
- Last but not the least, coroutines can also be implemented using threading though the performance will depend hugely on the JVM’s implementation of threads.
The coroutines are light-weight threads that run by sharing existing threads instead of having a dedicated system thread for themselves. This cooperative way of running by sharing and ensuring that the shared thread is not occupied more than necessary. ForkJoinTask in Java 7 and CompletableFuture in Java 8 provides explicit support regarding the shared thread execution. Now, if the coroutines get suspended for an indeterminate time, no computational resources are utilized and can resume execution only when awaited resources are available.
The first task of ours is to declare coroutines. There is a lack of a native mechanism to declare any objects that includes suspension; the only way is by implementing it through an API. It can be declared as an instance of the class coroutine, and esoco GmBH provides the Coroutine.first( ) method in order to invoke a new coroutine instance. Alternatively, one can call the constructor instance to perform the same task. Using Coroutine.then( ) users can provide additional functionality for complementing. Another major trait of the coroutine is that they are immutable, and the sequence of steps as defined can’t be altered.
Using CoroutineWriter and CoroutineReader, the coroutine can be serialized or deserialized. The Java’s in-built serialization function is used by default, and the coroutine’s state is made up using that. Apart from that, there are custom implementations to further control the serialization or deserialization and passed on to the CoroutineWriter and CoroutineReader for adding in different serialization formats like XML, JSON, or different serializer like XStream, Kryo, Jackson etc.
Serialization is an advanced feature, and familiarity of JVM byte code is highly recommended. The concept of serialization allows conversion of coroutine into a byte array and back from a byte array.
The typical use cases of serialization are:
- Using serialization, one can cache or checkpoint to a disk and write it back again.
- Over the wire coroutine transmission.
- Coroutine forking.
Another derivative of serialization is the versioning which enables developers to change the logic of coroutine along side still being able to load data using serialization from earlier versions.
Examples of Java Coroutines
Given below are the examples of Java Coroutines:
Example #1
A “Hello World” type example coroutines.
Syntax:
public static final class CoroutineDemo implements Coroutine {
@Override
public void run(Continuation c) {
System.out.println("Start of Main stream ");
for (int i = 0; i < 10; i++) {
echo(c, i);
}
}
private void echo(Continuation c, int x) {
System.out.println(x);
c.suspend();
}
}
CoroutineRunner runnerVar = new CoroutineRunner(new CoroutineDemo());
runnerVar.execute();
runnerVar.execute();
runnerVar.execute();
runnerVar.execute();
Output:
Example #2
Forking of an object.
Syntax:
public final class CoroutineDemo implements Coroutine {
@Override
public void run(Continuation c) {
System.out.println("Start of Main stream");
for (int i = 0; i < 10; i++) {
echo(c, i);
}
}
private void echo(Continuation c, int x) {
System.out.println(x);
c.suspend();
}
}
CoroutineRunner runner1 = new CoroutineRunner(new CoroutineDemo());
runner1.execute();
byte[] dumped = new CoroutineWriter().write(runner1);
CoroutineRunner runner2 = new CoroutineReader().read(dumped);
runner1.execute();
runner2.execute();
runner1.execute();
runner2.execute();
runner1.execute();
runner2.execute();
Output:
Conclusion
Through this article, we have looked at the working of Java coroutines. First, one needs to make sure that the POM and details of the maven build are properly instantiated and the build is done. With the different use case of a simple one and the forking, we are able to understand how 2 variables carry the information and if something gets updated in the root, the same thing is reflected back into the fork as well through serialization.
Recommended Articles
This is a guide to Java Coroutines. Here we discuss the introduction, how coroutines work in Java? and examples, respectively. You may also have a look at the following articles to learn more –