Multithreading in Java: Concurrency, Parallelism, and Essential Tools

Multithreading in Java: Concurrency, Parallelism, and Essential Tools

The ability to execute multiple tasks simultaneously is an essential element for the performance of modern applications. In Java, multithreading provides robust tools for creating, managing, and synchronizing concurrent tasks. Let’s explore the key concepts: thread creation, Executors, synchronized and Locks, and working with asynchronous tasks using Future and CompletableFuture.


Concurrency vs. Parallelism

Before diving into the tools, it’s important to differentiate two key concepts:

  • Concurrency: Involves executing multiple tasks in a way that they appear simultaneous but may actually share resources and processing time. Think of serving multiple customers at a restaurant, focusing on one at a time.
  • Parallelism: Refers to executing multiple tasks at the exact same time using multiple CPU cores. Imagine several chefs cooking different dishes simultaneously.

Java provides powerful tools to handle both, making it easier to build efficient and scalable systems.


1. Threads and Runnable

The Thread class and the Runnable interface are the simplest building blocks for creating threads. A basic example would be:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable(), "Thread-1");
        thread1.start();
    }
}        

2. Executor Framework

The Executor framework simplifies thread management, offering thread pools and greater control over execution.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            int taskId = i;
            executor.submit(() -> {
                System.out.println("Task " + taskId + " is running in " + Thread.currentThread().getName());
            });
        }

        executor.shutdown();
    }
}        

3. Future and CompletableFuture

The Future class helps work with asynchronous tasks, while CompletableFuture is more advanced, enabling composition and handling of future results.

A simple approach to handling asynchronous tasks using Future:

import java.util.concurrent.*;

public class FutureExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<Integer> future = executor.submit(() -> {
            Thread.sleep(1000);
            return 42;
        });

        System.out.println("Result: " + future.get());
        executor.shutdown();
    }
}        

CompletableFuture is an evolution of Future, enabling more elegant compositions and chaining:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("Executing task...");
            return 42;
        }).thenApply(result -> result * 2)
          .thenAccept(result -> System.out.println("Final result: " + result));
    }
}        

4. Synchronized and Locks

When multiple threads access the same resources, data consistency issues can arise. To prevent this, Java provides synchronized and Locks.

A straightforward way to ensure that only one thread can access a block of code or method at a time.

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}        

For more flexibility, you can use the java.util.concurrent.locks API.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CounterWithLock {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}        

Java provides utilities like ReentrantLock, CountDownLatch, and Semaphore to safely manage concurrency.

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " completed");
                latch.countDown();
            }).start();
        }

        latch.await();
        System.out.println("All threads have finished execution");
    }
}        


Conclusion

Mastering multithreading in Java is essential for building modern, high-performance systems. With tools like Executors, Locks, Future, and CompletableFuture, you can create safe, scalable applications ready to tackle the challenges of concurrency and parallelism.

🚀 Thanks for sharing! Inspired by this, I’ve kicked off my own 30-day journey to master multithreading in Java. 🧵💡 I'm just in Week 1, and there's already so much to explore! Follow my posts for insights, challenges, and key learnings along the way. Let’s level up together! 🔥💻

Like
Reply

Really nice tips. Threads can be threatening and your article helps to demystify it. Thanks!

Great advice! Thanks for sharing

Grateful for your perspective! 🙏

To view or add a comment, sign in

More articles by Jean Cardoso

  • Concurrency Patterns in Go

    In Go, concurrency patterns are a fundamental part of the language, designed to leverage the simplicity and efficiency…

    29 Comments
  • Introduction to Goroutines in Go: Simplified, High-Performance Concurrency

    Goroutines are one of the most powerful and intriguing features of Go, the programming language developed by Google…

    15 Comments
  • Exploring Virtual Threads Java 21

    Introduction With the release of Java 21, the language has introduced a new feature called Virtual Threads. This new…

    21 Comments

Explore content categories