Threads Basics in Java
Modern computers have multiple CPU cores, allowing them to perform numerous tasks simultaneously. In software, this is modeled through Concurrency and Multithreading.
A Process is an executing program (like your JVM instance) with its own isolated memory space. A Thread is the smallest sequence of programmed instructions that can be managed independently by a scheduler. Threads exist within a process and share the process's memory (heap), but maintain their own execution stack and program counter.
1. Creating Threads in Java
There are two primary ways to create a thread in standard Java.
Method A: Implementing the Runnable Interface (Preferred)
Runnable represents a task to be executed. Because Java only supports single inheritance, implementing an interface is preferred over extending the Thread class, as it leaves your class free to extend something else.
// 1. Define the task
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("Executing in: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
// 2. Create the task instance
Runnable task = new MyTask();
// 3. Pass the task to a new Thread object
Thread thread = new Thread(task, "Worker-Thread-1");
// 4. Start the thread (DO NOT call run() directly)
thread.start();
}
}
(With Java 8+, Runnable can be implemented via lambda expressions: Thread t = new Thread(() -> { ... });)
Method B: Extending the Thread Class
You can create a subclass of Thread and override its run() method.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running in: " + this.getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Starts a new thread of execution
}
}
Crucial Note: Always call start() to spawn a new thread. If you call run() directly, the code will just execute synchronously on the current thread, defeating the purpose of multithreading.
2. Thread Lifecycle and States
A Java Thread transitions through various states during its lifecycle, defined by the Thread.State enum:
- NEW: The
Threadobject is created, butstart()hasn't been called. - RUNNABLE: The thread is executing in the JVM (though it might be waiting for OS resources like CPU time).
- BLOCKED: The thread is waiting to acquire a monitor lock to enter or re-enter a
synchronizedblock/method. - WAITING: The thread is waiting indefinitely for another thread to perform a specific action (e.g., via
Object.wait()orThread.join()). - TIMED_WAITING: The thread is waiting for another thread to perform an action for up to a specified waiting time (e.g.,
Thread.sleep(ms)). - TERMINATED: The thread has completed execution of its
run()method.
3. Basic Thread Control Methods
Thread.sleep(long millis): Pauses the execution of the current thread for a specified time. Does not release monitor locks. ThrowsInterruptedException.t.join(): Causes the current thread to pause execution until threadtcompletes (dies). Useful for waiting for a worker to finish before aggregating results.Thread.yield(): A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.t.interrupt(): Interrupts threadt. If the thread is blocked insleep,wait, orjoin, it will wake up and throw anInterruptedException. Otherwise, its interrupt flag is set.
4. Daemon vs. User Threads
Java distinguishes between two types of threads:
- User Threads: Standard threads created by the application (like the main thread). The JVM will keep running as long as at least one user thread is alive.
- Daemon Threads: Background service threads (like the Garbage Collector). When all user threads terminate, the JVM automatically terminates all daemon threads and shuts down.
You can set a thread to be a daemon before starting it:
Thread bgThread = new Thread(() -> {
while(true) { /* Do background work */ }
});
bgThread.setDaemon(true); // Must be called before start()
bgThread.start();
5. Modern Thread Management: Executors
While manually creating new Thread() is fine for simple scripts, enterprise applications use Thread Pools via the Executor Framework (java.util.concurrent.ExecutorService).
Creating a new OS thread is expensive. Thread pools reuse a fixed number of threads to execute thousands of tasks, managing resource consumption and improving performance.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// Create a pool of 3 reusable threads
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Executing Task " + taskId + " in " + Thread.currentThread().getName());
});
}
// Always shut down the executor when done to allow JVM termination
executor.shutdown();
}
}