Java
The concurrency API was introduced in Java5 and then gradually enhanced with every Java release. A majority of concepts explained in this blog also work in older versions of Java. In this blog, the code samples focus on Java 8 and make use of lambda expressions and other new features.
Threads & Runnables
Today's operating systems support concurrency via processes and threads. Processes are instances of programs which run independent of each other, for example, if you start a java program the operating system raises a new process which runs in parallel to other programs. Inside those processes we can utilize threads to execute code concurrently, so we can use all the available cores of the CPU.
Java supports Threads since JDK 1.0. Before starting a new thread you have to specify the code to be executed by this thread, i.e called the task. This is done by implementing Runnable - a functional interface defining a single void no-args method run() as demonstrated in the below example:
Runnable rt = () -> {
String name = Thread.currentThread().getName();
System.out.println("Hello, " + name);
};
rt.run();
Thread thread = new Thread(rt);
thread.start();
System.out.println("Done!!!");
Since Runnable is a functional interface we can use Java 8 lambda expressions to print the current thread's name to the console.
OUTPUT :
Hello, main
Hello, Thread-0
Done!!!
Due to concurrent execution we can not predict if the runnable will execute before or after the "Done!!!" printing.
Working with the Thread class can be very tedious and also error-prone. Because of that, the Concurrency API was introduced in 2004 with the release of Java5. The API is present in package java.util.concurrent and contains various useful classes for handling concurrent programming.
Executors
The Concurrency API introduces the concept of ExecutorService in replacement for working with threads directly. Executors can run asynchronous tasks and manage a pool of threads, so we don't have to create new threads manually. All threads of the pool will be reused for recurring tasks, so we can run ‘n’ number of concurrent tasks throughout the life-cycle of our application with a single executor service.
Example of ExecuterService looks like :
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello!! " + threadName);
});
OUTPUT :
Hello!! pool-1-thread-1
The class Executors provides suitable factory methods for creating various kinds of executor services. In the above code we use an executor with a thread pool of size 1.
The result might look exactly the same to the above sample but when running the code you can notice a difference: java processes(Executors) have to be stopped explicitly - otherwise they keep listening for new tasks.
ExecuterService provides two methods,
shutdown() : This method waits for currently running tasks to finish.
shutdownNow() : This method interrupts all tasks and shutdown executors immediately.
Prefered way to shutdown the Executor :
try {
System.out.println("shutting down executor.");
es.shutdown();
es.awaitTermination(5, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.err.println("tasks interrupted.");
}
finally {
if (!es.isTerminated()) {
System.err.println("cancel non finished tasks.");
}
es.shutdownNow();
System.out.println("shutdown finished.");
}
The executor shuts down by waiting a certain amount of time for completion of currently running tasks. After waiting for a maximum of five seconds, the executor finally shuts down by interrupting all running tasks.
We are a 360-degree ERP development company that specializes in developing custom enterprise solutions to overcome diverse business challenges. Our development team uses an advanced tech stack and open-source ERP platforms to solve complex business problems with significant cost savings.