Java Multithreading
Java - Multithreading and Concurrency
Section titled “Java - Multithreading and Concurrency”Java is inherently a multithreaded programming language, allowing you to develop programs where multiple tasks run concurrently. A multithreaded program contains two or more parts (threads) that can execute seemingly simultaneously, making optimal use of available resources, especially on multi-core processors.
Multitasking vs. Multithreading: Multitasking involves sharing CPU resources among multiple processes (independent programs). Multithreading extends this concept within a single application, subdividing its operations into individual threads that share the same memory space. Each thread can run in parallel (on multiple cores) or concurrently (interleaved on a single core).
Concurrency enables writing responsive applications where long-running tasks don’t block the main execution flow (e.g., keeping a UI responsive while performing background calculations).
Life Cycle of a Thread:
Section titled “Life Cycle of a Thread:”A thread progresses through several states during its lifetime:
- NEW: The thread has been created but has not yet started execution (i.e., its
start()method hasn’t been called). - RUNNABLE: The thread is eligible to be run by the thread scheduler. This state includes both threads actually running on the CPU and those ready to run but waiting for the scheduler to allocate CPU time.
- BLOCKED: The thread is waiting to acquire a monitor lock (e.g., entering a
synchronizedblock or method currently held by another thread). - WAITING: The thread is waiting indefinitely for another thread to perform a specific action. This occurs after calls like
Object.wait(),Thread.join(), orLockSupport.park(). - TIMED_WAITING: The thread is waiting for a specified period for another thread to act or for the time to elapse. This occurs after calls like
Thread.sleep(long millis),Object.wait(long timeout),Thread.join(long millis),LockSupport.parkNanos(), orLockSupport.parkUntil(). - TERMINATED: The thread has completed its execution (its
run()method has finished) or has otherwise terminated.
Thread Priorities:
Section titled “Thread Priorities:”Every Java thread has a priority (an integer between Thread.MIN_PRIORITY (1) and Thread.MAX_PRIORITY (10), with Thread.NORM_PRIORITY (5) as the default).
In theory, higher-priority threads should get preference from the scheduler. However, the actual behavior of thread priorities is highly dependent on the underlying operating system and JVM implementation. Relying on priorities for program correctness is not recommended and can lead to non-portable behavior or issues like starvation. Use proper synchronization mechanisms instead.
Creating Threads: The Traditional Ways
Section titled “Creating Threads: The Traditional Ways”There are two fundamental ways to define the code that a thread will execute:
1. Implementing the Runnable Interface (Preferred)
Section titled “1. Implementing the Runnable Interface (Preferred)”This is generally preferred as it separates the task (Runnable) from the execution mechanism (Thread) and allows your class to extend another class if needed.
- Create a class that implements the
java.lang.Runnableinterface. - Implement the
public void run()method within that class. This method contains the code the thread will execute. - Create an instance of your
Runnableclass. - Create an instance of
java.lang.Thread, passing yourRunnableobject to its constructor. - Call the
start()method on theThreadobject. This creates a new system thread and invokes therun()method of yourRunnableobject in that new thread.
Example (Implementing Runnable):
Section titled “Example (Implementing Runnable):”import java.util.concurrent.TimeUnit;
class MyRunnableTask implements Runnable { private final String taskName;
public MyRunnableTask(String name) { this.taskName = name; System.out.println("Creating task: " + taskName); }
@Override public void run() { System.out.println("Running task: " + taskName); try { for (int i = 3; i > 0; i--) { System.out.println("Task " + taskName + ", Count: " + i); // Use TimeUnit for clearer sleep duration TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); System.out.println("Task " + taskName + " interrupted."); } System.out.println("Task " + taskName + " finished."); }}
public class RunnableTest { public static void main(String[] args) { System.out.println("Main thread started.");
MyRunnableTask task1 = new MyRunnableTask("Task-A"); Thread thread1 = new Thread(task1);
MyRunnableTask task2 = new MyRunnableTask("Task-B"); Thread thread2 = new Thread(task2);
// Start the threads thread1.start(); thread2.start();
System.out.println("Main thread finished starting tasks."); // Note: main thread might finish before task threads complete }}2. Extending the Thread Class
Section titled “2. Extending the Thread Class”This approach is less flexible because Java only allows single inheritance.
- Create a class that extends
java.lang.Thread. - Override the
public void run()method in your class. - Create an instance of your custom
Threadsubclass. - Call the
start()method on the instance. This performs the same action as in theRunnablecase.
Example (Extending Thread):
Section titled “Example (Extending Thread):”import java.util.concurrent.TimeUnit;
class MyThread extends Thread { public MyThread(String name) { super(name); // Pass name to Thread constructor System.out.println("Creating thread: " + getName()); }
@Override public void run() { System.out.println("Running thread: " + getName()); try { for (int i = 3; i > 0; i--) { System.out.println("Thread " + getName() + ", Count: " + i); TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("Thread " + getName() + " interrupted."); } System.out.println("Thread " + getName() + " finished."); }}
public class ThreadExtensionTest { public static void main(String[] args) { System.out.println("Main thread started.");
MyThread threadA = new MyThread("Thread-A"); MyThread threadB = new MyThread("Thread-B");
threadA.start(); threadB.start();
System.out.println("Main thread finished starting threads."); }}Modern Concurrency: The java.util.concurrent Package
Section titled “Modern Concurrency: The java.util.concurrent Package”While directly managing Thread objects is fundamental, modern Java development heavily relies on the higher-level abstractions provided in the java.util.concurrent package (introduced in Java 5). This package offers more robust, efficient, and easier-to-use concurrency utilities.
Key Components:
Section titled “Key Components:”- Executor Framework: Decouples task submission from task execution.
ExecutorServiceis the primary interface, often created using factory methods in theExecutorsclass (e.g.,Executors.newFixedThreadPool(),Executors.newCachedThreadPool()). This is the recommended way to manage threads in most applications. - Callable
and Future : Callableis similar toRunnablebut can return a result and throw checked exceptions. When submitted to anExecutorService, it returns aFutureobject, which represents the result of an asynchronous computation. - Concurrent Collections: Thread-safe collection implementations like
ConcurrentHashMap,CopyOnWriteArrayList,BlockingQueue. - Synchronizers: Utilities for coordinating threads, such as
Semaphore,CountDownLatch,CyclicBarrier,Phaser. - Locks: More flexible locking mechanisms than the intrinsic
synchronizedkeyword, likeReentrantLock,ReadWriteLock.
Example using ExecutorService:
Section titled “Example using ExecutorService:”import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;
public class ExecutorServiceTest { public static void main(String[] args) { System.out.println("Main thread started.");
// Create a fixed-size thread pool ExecutorService executor = Executors.newFixedThreadPool(2);
// Submit Runnable tasks executor.submit(new MyRunnableTask("PoolTask-1")); executor.submit(new MyRunnableTask("PoolTask-2")); executor.submit(new MyRunnableTask("PoolTask-3")); // Will wait if pool is busy
// Initiate graceful shutdown executor.shutdown(); // Stops accepting new tasks, waits for existing ones try { // Wait for tasks to complete for a maximum time if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { System.err.println("Tasks did not complete in time, forcing shutdown."); executor.shutdownNow(); // Attempt to interrupt running tasks } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); }
System.out.println("Main thread finished."); }
// Reusing MyRunnableTask from the previous example static class MyRunnableTask implements Runnable { // ... (same implementation as before) ... private final String taskName;
public MyRunnableTask(String name) { this.taskName = name; System.out.println("Creating task: " + taskName); }
@Override public void run() { System.out.println(Thread.currentThread().getName() + " running task: " + taskName); try { for (int i = 3; i > 0; i--) { System.out.println("Task " + taskName + ", Count: " + i); TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.out.println("Task " + taskName + " interrupted."); } System.out.println(Thread.currentThread().getName() + " finished task: " + taskName); } }}Essential Thread Methods:
Section titled “Essential Thread Methods:”Key methods available in the Thread class:
| SN | Method | Description |
|---|---|---|
| 1 | public void start() | Starts the thread’s execution by calling its run() method in a new thread context. |
| 2 | public void run() | The entry point for the thread’s execution logic. Directly calling run() executes the code in the current thread, not a new one. |
| 3 | public final void join()public final void join(long millis) | Waits for this thread to terminate. The calling thread blocks until the target thread dies or the timeout occurs. |
| 4 | public void interrupt() | Interrupts this thread. Sets the thread’s interrupt status flag. If the thread is blocked in sleep(), wait(), or join(), it throws an InterruptedException. |
| 5 | public static boolean interrupted() | Checks if the current thread has been interrupted and clears the interrupted status. |
| 6 | public boolean isInterrupted() | Checks if this thread has been interrupted without clearing the status. |
| 7 | public final boolean isAlive() | Returns true if the thread has started and has not yet died. |
| 8 | public static void sleep(long millis) | Causes the current thread to pause execution for the specified milliseconds (subject to scheduler precision). Throws InterruptedException. |
| 9 | public static Thread currentThread() | Returns a reference to the currently executing thread object. |
| 10 | public final void setDaemon(boolean on) | Marks this thread as a daemon thread. The JVM exits when only daemon threads are running. |
Synchronization and Thread Safety
Section titled “Synchronization and Thread Safety”When multiple threads access shared, mutable data, you need mechanisms to prevent race conditions and ensure data consistency. This is known as thread safety.
Key concepts include:
- Synchronization: Using mechanisms like the
synchronizedkeyword orjava.util.concurrent.locks.Lockimplementations to ensure only one thread can access a critical section of code or shared resource at a time. - Atomicity: Ensuring an operation completes entirely or not at all, without interference from other threads (e.g., using classes from
java.util.concurrent.atomic). - Visibility: Ensuring changes made to shared variables by one thread are visible to other threads (using
volatilekeyword or synchronization). - Deadlock: A situation where two or more threads are blocked forever, each waiting for the other to release a resource.
- Immutable Objects: Objects whose state cannot be changed after creation are inherently thread-safe.
Developing correct concurrent applications requires careful design and understanding of these concepts. Using the utilities from java.util.concurrent often simplifies achieving thread safety compared to manual locking with synchronized.