Multithreading in Java is a feature that allows concurrent execution of two or more parts of a program for maximum utilization of the CPU. Each part of such a program is called a thread. So, threads are light-weight processes within a process.
A thread does not just "run." It moves through a specific set of states managed by the JVM and the underlying Operating System. Understanding these states is critical for debugging deadlocks and performance issues.
| State | Description |
|---|---|
| New | Thread is created but start() hasn't been called. |
| Runnable | Ready to run and waiting for CPU time from the scheduler. |
| Blocked | Waiting for a monitor lock to enter a synchronized block. |
| Waiting | Waiting indefinitely for another thread to perform an action. |
| Timed Waiting | Waiting for a specified amount of time (e.g., sleep(1000)). |
| Terminated | Execution has finished. |
Java provides two primary mechanisms to define a thread's task. While both achieve the same goal, one is generally preferred in professional development.
Create a class that extends Thread and override the run() method. This is simpler but limits your class from extending anything else (Java's single inheritance rule).
Implement the Runnable interface. This is more flexible as your class can still extend another class. It separates the "Task" from the "Thread Runner."
When multiple threads try to access the same data (like a bank balance) at the same time, you get a Race Condition. Java uses the synchronized keyword to ensure that only one thread can access a resource at a time.
Threads often need to talk to each other. For example, a "Producer" thread creates data and needs to tell a "Consumer" thread that data is ready. This is handled by three methods from the Object class:
wait(): Tells the current thread to give up the lock and go to sleep until notified.notify(): Wakes up a single thread that is waiting on the object's monitor.notifyAll(): Wakes up all threads waiting on the monitor.This example demonstrates how two threads can safely increment a shared variable using synchronization.
In a multi-core system, threads might cache variables in CPU registers for speed. This means one thread might not see a change made by another. The volatile keyword tells the JVM: "Always read this variable from main memory, never cache it."
A **Deadlock** happens when Thread A is waiting for a lock held by Thread B, while Thread B is waiting for a lock held by Thread A. Neither can move, and the program freezes forever.
Prevention: Always acquire locks in the same order across all threads, or use tryLock() from the java.util.concurrent package.
Q: What is the difference between start() and run()?
A: start() creates a new call stack and triggers the thread. Calling run() directly just executes the code in the current thread like a normal method call.
Q: What does join() do?
A: It pauses the execution of the current thread until the thread on which join() was called finishes. It's used for synchronization points.
Q: Why is stop() deprecated?
A: Because it is inherently unsafe. Stopping a thread abruptly leaves shared data in an inconsistent state and doesn't release locks properly. Use "interrupts" instead.
Java Multithreading is what makes applications feel fluid and professional. While it introduces complexities like race conditions and deadlocks, mastering synchronization and thread communication allows you to build high-performance systems. In modern Java, we also use the ExecutorService and CompletableFuture to manage threads even more efficiently.
Next: Mastering ExecutorService →