Mastering Multithreading in Java: From Threads to Virtual Threads

Illustration for Mastering Multithreading in Java: From Threads to Virtual Threads
By Last updated:

Multithreading is a powerful feature in Java that allows concurrent execution of two or more parts of a program. It plays a crucial role in performance-critical applications such as web servers, real-time systems, gaming engines, and high-throughput services.

In the modern computing landscape where multi-core processors are the norm, mastering multithreading is essential for building responsive and scalable applications.


What is Multithreading?

Multithreading refers to the concurrent execution of two or more threads (smaller units of a process). It enables a program to perform multiple operations simultaneously, utilizing CPU cores efficiently.

Real-world analogy

Think of a thread pool like a team of factory workers — each worker (thread) is assigned a task. Once completed, they take the next one from the queue.


Java Threading API

Core classes and interfaces

  • Thread
  • Runnable
  • Callable<V> (returns result, supports checked exceptions)

Creating threads

public class MyThread extends Thread {
    public void run() {
        System.out.println("Running in MyThread");
    }
}
public class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Running in MyRunnable");
    }
}
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> System.out.println("Running in thread pool"));

Thread Lifecycle and States

NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED

Key methods

  • start() vs run()
  • join()
  • sleep(ms)
  • yield()
  • interrupt()

Memory Model and Visibility

  • Java Memory Model (JMM) defines how threads interact through memory
  • volatile ensures visibility (but not atomicity)
  • synchronized ensures mutual exclusion and memory visibility

Thread Coordination

  • wait(), notify(), notifyAll()
  • join() for waiting on other threads
  • sleep() for delaying execution

Locking and Synchronization

  • synchronized blocks and methods
  • ReentrantLock
  • ReadWriteLock
  • StampedLock

java.util.concurrent Tools

  • ExecutorService, ThreadPoolExecutor
  • Future, CompletableFuture
  • ForkJoinPool, RecursiveTask
  • BlockingQueue, ConcurrentHashMap

Real-World Scenarios

  • Producer-Consumer using BlockingQueue
  • Thread pools for task management
  • Parallel file processing

Java Version Tracker

📌 What's New in Java Versions?

Java 8

  • Lambdas for Runnable
  • CompletableFuture
  • Parallel streams

Java 9

  • Flow API (Reactive Streams)

Java 11+

  • Minor enhancements in CompletableFuture

Java 21

  • Structured concurrency
  • Virtual threads (Project Loom)
  • Scoped values

Best Practices and Common Pitfalls

✅ Best Practices

  • Prefer ExecutorService over manual thread creation
  • Use immutable objects for shared data
  • Use thread-safe collections from java.util.concurrent

🚫 Common Pitfalls

  • Race conditions
  • Deadlocks
  • False sharing
  • Uncaught exceptions in threads

Multithreading Design Patterns

  • Worker Thread
  • Future Task
  • Thread-per-message
  • Producer-Consumer

Conclusion and Key Takeaways

  • Multithreading improves performance and responsiveness
  • Java provides robust APIs and concurrency tools
  • Understand JMM, synchronization, and advanced constructs
  • Prefer high-level constructs like thread pools or virtual threads

FAQs

Q1: Why shouldn't we call run() directly?
A: It executes in the same thread instead of a new one. Use start() to spawn a new thread.

Q2: What is the difference between volatile and synchronized?
A: volatile ensures visibility; synchronized ensures both mutual exclusion and visibility.

Q3: Can synchronized methods lead to deadlocks?
A: Yes, if threads acquire locks in different orders.

Q4: What is the thread-safe way to increment a counter?
A: Use AtomicInteger.incrementAndGet().

Q5: When should I use ReentrantLock over synchronized?
A: When you need try-locking, timed locking, or interruptible locking.

Q6: What is false sharing?
A: When multiple threads modify variables that reside on the same cache line, causing performance degradation.

Q7: How do thread pools help performance?
A: They reduce the overhead of thread creation and reuse existing threads.

Q8: What are virtual threads in Java 21?
A: Lightweight threads managed by the JVM, ideal for I/O-bound workloads.

Q9: What is structured concurrency?
A: A model in Java 21+ to manage thread lifecycles hierarchically for safer concurrency.

Q10: How do I debug race conditions?
A: Use thread dumps, logging, and tools like jconsole or VisualVM to trace thread behavior.