From a standalone application to web applications to the sophisticated cloud integration of enterprise, Java has been updating itself to accommodate various features that support the change. Especially, frameworks like Spring have come up with modules like Spring Boot, Batch, and Integration, which comply with most of the cloud integration features. As a language, Java has a great support for programs to be written using multithreaded distributed objects. In this model, an application contains numerous heavyweight processes that communicate using messaging or Remote Method Invocations (RMI). Each heavyweight process contains several lightweight processes, which are termed as threads in Java. Threads can communicate through the shared memory. Such software architecture reflects the hardware that is configured to be extensively accessible.
By assuming that there is, at most, one thread per process or by ignoring the parallelism within one process, it is the usual model of a distributed system. The purpose of making the logically simple is that the distributed program is more object-oriented because data in a remote object can be accessed only through an explicit messaging or a remote procedure call (RPC).
The object-orientated model promotes reusability as well as design simplicity. Furthermore, a large shared data structure has the requirement of shared processing, which is possible through object orientation and letting the process of execution be multithreaded. The programming should carry the responsibility of splitting the larger data structure across multiple heavyweight processes.
Programming language, which wants to support concurrent programming, should be able to instruct the process structure, and how several processes communicate with each other and synchronize. There are many ways the Java program can specify the process structure or create a new process. For example, UNIX processes are tree structured containing a unique process ID (pid) for each process. fork and wait are the commands to create and synchronize the processes. The fork command creates a child process from a parent process with a parent process address space copy:
pid = fork();
if (pid != 0 ) {
cout << "This is a parent process";
}
else {
cout << "This is a child process";
}
Java has a predefined class called Thread to enable concurrency through creating thread objects. A class can extend the Thread class if it should be executed in a separate thread, override the run() method, and execute the start() method to launch that thread:
public class NewThread extends Thread {
public void run() {
System.out.println("New Thread executing!");
}
public static void main(String[] args) {
Thread t1 = new NewThread();
t1.start();
}
}
In the cases where a class has to extend another class and execute as a new thread, Java supports this behavior through the interface Runnable, as shown in the following example:
public class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public class Mammal extends Animal implements Runnable {
public Mammal(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("The name of the Animal is : " + this.getName());
}
}
public static void main(String[] args) {
Animal firstAnimal = new Mammal("Tiger");
Thread threadOne = new Thread((Runnable) firstAnimal);
threadOne.start();
Animal secondAnimal = new Mammal("Elephant");
Thread threadTwo = new Thread((Runnable) secondAnimal);
threadTwo.start();
}
}
In the following example of Fibonacci numbers, a thread waits for completing the execution of other threads using the Join mechanism. Threads can carry the priority as well to set the importance of one thread over the other to execute before:
public class Fib extends Thread
{
private int x;
public int answer;
public Fib(int x) {
this.x = x;
}
public void run() {
if( x <= 2 )
answer = 1;
else {
try {
Fib f1 = new Fib(x-1);
Fib f2 = new Fib(x-2);
f1.start();
f2.start();
f1.join();
f2.join();
answer = f1.answer + f2.answer;
}
catch(InterruptedException ex) { }
}
}
public static void main(String[] args) throws Exception
{
try {
Fib f = new Fib( Integer.parseInt(args[0]) );
f.start();
f.join();
System.out.println(f.answer);
}
catch(Exception ex) {
System.err.println("usage: java Fib NUMBER");
}
}
}
With the latest Java version, a Callable interface is introduced with a @FunctionalInterface annotation. With the help of this feature, we can create Callable objects using lambda expressions as follows:
Callable<Integer> callableObject = () -> { return 5 + 9; };
The preceding expression is equivalent to the following code:
Callable<Integer> callableObject = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 5 + 6;
}
};
Following is the complete example with Callable and Future interfaces and lambda expressions for handing concurrent processing in Java 9:
package threads;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class JavaCallableThreads {
public static void main(String[] args) {
final List<Integer> numbers = Arrays.asList(1,2,3,4,5);
Callable<Integer> callableObject = () -> {
int sum = numbers.stream().mapToInt(i -> i.intValue()).sum();
return sum;
};
ExecutorService exService = Executors.newSingleThreadExecutor();
Future<Integer> futureObj = exService.submit(callableObject);
Integer futureSum=0;
try {
futureSum = futureObj.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("Sum returned = " + futureSum);
}
}
Modern Java enterprise applications have evolved through messaging (through message queue), web services, and writing microservices based distributed application like docker with applications deployed on cloud computing services like RedHat OpenShift, Amazon Web Services (AWS), Google App Engine and Kubernetes.
We will discuss the Java 9 support for such application development and deployment in detail in the coming chapters.