Concurrency in Java 8— Oversimplified

Saurav Samantray
3 min readMar 1, 2021

--

Introduction

Processes and threads are ways of achieving concurrency.

Processes are instances of a program that runs independently of each other. The numerous applications you are running right now, chrome, eclipse, java program, all are different processes running in your system and managed by the OS.

Each process internally can achieve concurrency using threads. For example, your java processes can spawn a new thread( apart from the main thread) to get something done asynchronously or parallel to the main thread.

Runnable

The unit which holds the logic of your task. Assume a dummy task where we want to trigger an email. From java 8 Runnable is a functional interface and can be implemented using the lambda function.

Runnable task = () -> {    System.out.println("Triggering email from: " + Thread.currentThread().getName());};task.run();

On executing the above piece of code, you would get below output

Triggering email from: main

While it was fun, it still was executed by the main thread itself and not in another parallel thread that we want.

Thread

An instance of the Thread class accepts a Runnable and spawns a new thread to execute the task defined in it. Let us update the previous code as below

Runnable task = () -> {    System.out.println("Triggering email from: " + Thread.currentThread().getName());};Thread thread = new Thread(task);thread.start();System.out.println("This is the " + Thread.currentThread().getName() + " thread");

Output:

Triggering email from: Thread-0
This is the main thread

Ahh, now we got the runnable to be executed in a separate thread. But….

Executors

The problem with directly using threads is you must manage the whole thing. ExecutorService was introduced as a superior implementation that can not only execute tasks asynchronously but also manage the pool of threads.

Runnable task = () -> {    System.out.println("Triggering email from: " + Thread.currentThread().getName());};ExecutorService executor = Executors.newFixedThreadPool(1);executor.submit(task);

Yipe, I have the same result as before with more control over the thread pool. Cool! But wait, why hasn’t the main program terminated?

Executors must be stopped manually. It can be done using shutdown(), which waits for all running tasks to finish, or shutdownNow() which will interrupt all running tasks and shutdown the executor immediately.

Callable and Future

Runnable is fine for defining a task but its return type is void. What if you want to return some data after the execution of the task? You can use Callable which is like Runnable but returns an instance of Future class which the current thread can peek into to get a response after the task is complete.

Callable<Integer> task = () -> {    System.out.println("Processing huge amount of data: "+Thread.currentThread().getName());    TimeUnit.SECONDS.sleep(2);    return 1000000;};ExecutorService executor = Executors.newFixedThreadPool(1);Future<Integer> future = executor.submit(task);Integer result = future.get();System.out.println(result);executor.shutdown();

.get() method in Future blocks the current thread till the callable has completed.

This is just the tip of the iceberg as far as concurrency in Java goes. I decided to start this oversimplified series as a quick learning platform to help newbies grasp complex concepts. It is highly recommended that to continue reading on concurrency and get a deeper understanding as it is a important topic in Java and over the years has evolved become really mature.

Happy Learning!

--

--