Core Methods of Threads
When working with multi-threaded applications in Java, the java.lang.Thread class serves as the fundamental building block. Mastering concurrency requires more than just knowing how to launch a thread; it requires a deep understanding of how threads interact, how they are scheduled, and how they communicate.
To navigate the complexities of thread management effectively, a developer should always keep three critical questions in mind when evaluating a method:
- Targeting: Does this method operate on the current thread or a target thread?
- Locking: Does the method release the monitor lock if one is held?
- Interruption: Does the method respond to an interrupt signal?
One of the most common mistakes for beginners is confusing thestart()method with therun()method. While they might seem interchangeable, their underlying mechanics are fundamentally different.
start(): This method is the gateway to true multi-threading. When you callstart(), you are notifying the Java Virtual Machine (JVM) to allocate system resources and create a new call stack. The thread then enters the RUNNABLE state, and the scheduler eventually invokes therun()method in this new execution context.run(): This is simply a regular method defined in theRunnableinterface. If you callrun()directly, no new thread is created. Instead, the code executes synchronously within the current thread, defeating the entire purpose of concurrency.
Thread t = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
t.start(); // Correct: A new thread is spawned
// t.run(); // Incorrect: Executes in the caller's thread
Note: A thread object can only be started once. Attempting to call start() on a thread that has already been started or has completed its execution will throw an IllegalThreadStateException.
Scheduling and Pausing: sleep() and yield()
The JVM and the underlying operating system work together to manage how threads share CPU time. Two methods often used to influence this scheduling are sleep() and yield().
sleep(long millis)
The sleep() method causes the current thread to suspend execution for a specified duration, moving it into the TIMED_WAITING state.
- Crucial Detail:
sleep()does not release any locks the thread currently holds. This can lead to deadlocks if not managed carefully. - Exception Handling: It throws a checked
InterruptedException, meaning you must explicitly handle the possibility of the thread being interrupted while sleeping.
yield()
The yield() method is a "hint" to the thread scheduler that the current thread is willing to give up its current use of the processor to allow other threads of the same priority to run.
- Non-binding: The scheduler is free to ignore this hint.
- State: The thread remains in the RUNNABLE state; it does not enter a waiting state and does not release locks.
Synchronizing Execution with join()
Sometimes, a thread needs to wait for the completion of another specific thread before it can proceed. This is where join() becomes essential.
When thread A calls threadB.join(), thread A will transition into a waiting state and will not resume until threadB has finished its execution (terminated). This is a common pattern when a main thread must wait for worker threads to finish their computations before aggregating results.
Thread worker = new Thread(() -> {
// Perform heavy computation
});
worker.start();
// The main thread waits here until 'worker' is dead
worker.join();
System.out.println("Worker finished, proceeding with main logic.");
You can also use join(long millis) to specify a maximum timeout, preventing the calling thread from waiting indefinitely if the target thread hangs.
The Interruption Mechanism
In Java, interruption is cooperative, not preemptive. You cannot forcibly "kill" a thread safely from the outside; instead, you must signal to the thread that it should stop what it is doing.
There are three primary methods involved in this mechanism:
interrupt(): This is called on a target thread. It sets the thread's "interrupted status" flag. If the target thread is currently blocked in a method likesleep(),wait(), orjoin(), the method will immediately throw anInterruptedExceptionand clear the interrupt flag.isInterrupted(): An instance method that checks the interrupted status of a target thread without clearing the flag.interrupted(): A static method that checks the interrupted status of the current thread and, importantly, clears the flag.
Best Practice: When catching an InterruptedException, never "swallow" it (i.e., leaving the catch block empty). You should either re-throw it or restore the interrupt status by calling Thread.currentThread().interrupt() so that higher-level code is aware of the interruption request.
Thread Metadata and Attributes
To manage and debug complex systems, you need to inspect the properties of your threads.
Thread.currentThread(): Returns a reference to the thread currently executing the code.- Naming: Using
setName(String name)andgetName()is highly recommended. In a production environment with hundreds of threads, seeing "Thread-14" in a stack trace is useless, whereas "OrderProcessingThread-01" is invaluable. - Daemon Threads: By calling
setDaemon(true), you mark a thread as a background service provider. Daemon threads do not prevent the JVM from exiting. When all non-daemon (user) threads have finished, the JVM will shut down, abruptly terminating any remaining daemon threads. This must be configured before the thread is started. getState(): Returns the current state of the thread (e.g.,NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING, orTERMINATED).
Inter-Thread Communication: wait(), notify(), and notifyAll()
While the methods above are part of the Thread class, the core mechanisms for thread collaboration—wait(), notify(), and notifyAll()—are actually defined in the Object class. This is because these methods rely on the monitor lock (intrinsic lock) associated with an object.
The Rules of Engagement:
- Ownership: You must hold the object's monitor (via a
synchronizedblock) before calling these methods. Failure to do so results in anIllegalMonitorStateException. wait(): The current thread releases the lock and enters theWAITINGstate until another thread invokesnotify()ornotifyAll()on the same object.notify(): Wakes up a single, arbitrarily chosen thread that is waiting on this object's monitor.notifyAll(): Wakes up all threads currently waiting on this object's monitor.
The "Spurious Wakeup" Problem
A thread can sometimes wake up from a wait() call even without a notify() signal being sent. To handle this, always invoke wait() inside a while loop that checks the condition being waited for, rather than an if statement.
synchronized (lock) {
while (!conditionMet) {
lock.wait(); // Releases lock and waits
}
// Proceed once condition is actually met
}
Summary Checklist
| Method | Target | Releases Lock? | Responds to Interrupt? |
|---|---|---|---|
start() |
N/A | N/A | N/A |
sleep() |
Current | No | Yes |
yield() |
Current | No | No |
join() |
Target | No (waits for target) | Yes |
wait() |
Current | Yes | Yes |
interrupt() |
Target | N/A | N/A |