Multithreading allows Java programs to perform multiple operations concurrently, maximizing CPU utilization and improving responsiveness. Two foundational ways to create threads in Java are by extending the Thread
class or implementing the Runnable
interface.
Understanding the differences between these approaches is crucial for designing maintainable, scalable concurrent applications.
Thread Class vs Runnable Interface: The Basics
Extending Thread
class MyThread extends Thread {
public void run() {
System.out.println("Thread using Thread class");
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
Implementing Runnable
class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread using Runnable interface");
}
}
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
Core Concept: What Is Multithreading?
Multithreading is the ability of a CPU to execute multiple threads concurrently. It enhances:
- Responsiveness in GUIs
- Throughput in server applications
- Resource utilization in compute-intensive apps
Thread Lifecycle in Java
NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED
Lifecycle Methods
start()
– begins execution of the threadrun()
– contains the logicjoin()
– wait for thread to finishsleep()
– pause current threadinterrupt()
– signal interruption
Java Memory Model & Thread Visibility
volatile
ensures visibility of updates to variablessynchronized
ensures atomicity and visibility- Avoid stale data by understanding how threads interact through the heap
Coordination and Communication
wait()
,notify()
,notifyAll()
– used for condition-based coordinationjoin()
– synchronize executionsleep()
– used for delays, not communication
Synchronization Tools
synchronized
blocks/methodsReentrantLock
– flexible and interruptibleReadWriteLock
– improves concurrency for readsStampedLock
– supports optimistic reads
High-Level Concurrency Tools
ExecutorService
,ThreadPoolExecutor
ForkJoinPool
,RecursiveTask
Future
,CompletableFuture
BlockingQueue
,ConcurrentHashMap
Real-World Examples
Producer-Consumer
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
Thread Pool
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> System.out.println("Task"));
executor.shutdown();
Parallel File Processing
Use thread pools to assign parts of file processing to multiple threads.
Pros and Cons
Feature | Thread Class | Runnable Interface |
---|---|---|
Inheritance | Restricts single inheritance | Allows flexibility |
Code Sharing | Less reusable | More reusable and flexible |
Separation | Tightly couples thread and logic | Clean separation |
Preferred Use | For simple use cases | For most real-world scenarios |
Java Version Tracker
📌 What's New in Java Versions?
Java 8
- Lambda support for Runnable
CompletableFuture
- Parallel Streams
Java 9
- Flow API for reactive streams
Java 11+
- Small enhancements in
CompletableFuture
Java 21
- Virtual Threads
- Structured Concurrency
- Scoped Values
Best Practices
- Prefer
Runnable
over extendingThread
- Use thread pools via
Executors
- Avoid shared mutable state
- Use higher-level APIs for complex tasks
Common Pitfalls
- Calling
run()
instead ofstart()
- Race conditions
- Deadlocks from nested locks
- Resource leaks from unclosed executors
Multithreading Design Patterns
- Worker Thread Pattern
- Future Task Pattern
- Thread-per-Message
- Producer-Consumer Pattern
Conclusion and Key Takeaways
- Use
Runnable
for flexibility and reusability - Extend
Thread
only for quick prototypes or tiny apps - Always prefer high-level concurrency APIs for thread safety and scalability
- Understand the underlying lifecycle and memory implications
FAQs
Q1: Why is Runnable
preferred over Thread
?
A: Runnable decouples task logic from threading mechanism, allowing reuse and better design.
Q2: What happens if I call run()
instead of start()
?
A: The thread runs in the current thread and doesn’t spawn a new one.
Q3: Can I pass parameters to threads?
A: Yes, via constructors or shared variables (with care).
Q4: Is synchronized
enough for all scenarios?
A: Not always. Use locks for finer control and features like timeout, fairness.
Q5: What's the benefit of ExecutorService
?
A: Manages a pool of threads efficiently, avoids overhead of thread creation.
Q6: Can I stop a thread externally?
A: Not directly. Use interruption via interrupt()
and check isInterrupted()
.
Q7: What's the difference between Callable
and Runnable
?
A: Callable
can return results and throw exceptions.
Q8: How are virtual threads different?
A: Virtual threads (Java 21) are lightweight, scalable threads managed by JVM.
Q9: What is structured concurrency?
A: A model to manage thread lifecycles hierarchically for better error handling.
Q10: Can I combine Runnable with Thread subclassing?
A: Technically yes, but it's discouraged due to poor separation of concerns.