Multithreading in Java
Multithreading in Java allows multiple threads to execute concurrently, enabling better CPU utilization, faster execution, and more efficient handling of tasks. Multithreading is essential for performing complex tasks such as parallel processing, managing I/O operations, or handling multiple user requests simultaneously.
In Java, threads are the smallest unit of execution. Each thread can run concurrently with other threads, and they share the same memory space, allowing communication between them.
Key Concepts of Multithreading
- Thread: A lightweight process that can run concurrently with other threads.
- Process: A heavyweight entity that consists of one or more threads.
- Concurrency: The ability to run multiple threads at the same time.
- Parallelism: The simultaneous execution of tasks on multiple processors or cores.
Creating Threads in Java
In Java, there are two primary ways to create threads:
- Extending the Thread Class
- Implementing the Runnable Interface
1. Extending the Thread Class
You can create a thread by extending the Thread
class and overriding the run()
method.
Example:
class MyThread extends Thread {
@Override
public void run() {
// Code to be executed in the thread
System.out.println("Thread is running: " + Thread.currentThread().getName());
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // Start the thread
}
}
Explanation:
Thread
class: Java provides a built-inThread
class that can be extended to create a custom thread.run()
method: This is the entry point for the thread's execution. Code insiderun()
will be executed when the thread starts.start()
method: Thestart()
method is used to begin the execution of the thread. It internally calls therun()
method.
2. Implementing the Runnable Interface
Alternatively, you can create a thread by implementing the Runnable
interface and overriding its run()
method. This approach is preferred when your class already extends another class (since Java supports single inheritance).
Example:
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running using Runnable: " + Thread.currentThread().getName());
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start(); // Start the thread
}
}
Explanation:
Runnable
interface: TheRunnable
interface defines a single methodrun()
, which is executed when the thread is started.- Thread Creation: You create an instance of the
Thread
class, passing theRunnable
object to its constructor.
Thread Lifecycle in Java
A thread goes through several states in its lifecycle:
- New: A thread is created but not started.
- Runnable: A thread is ready to run, but the CPU may not be executing it yet.
- Blocked: A thread is blocked and cannot run (waiting for a resource, like I/O or a lock).
- Waiting: A thread is waiting indefinitely for another thread to perform a specific action.
- Timed Waiting: A thread is waiting for a specific amount of time (e.g., sleep).
- Terminated: A thread has finished execution.
Thread Methods
Java's Thread
class provides several useful methods to control thread behavior:
start()
: Begins the execution of the thread. It calls therun()
method.sleep(long millis)
: Causes the current thread to sleep for the specified number of milliseconds.join()
: Allows one thread to wait for another thread to finish before it continues.interrupt()
: Interrupts a thread.getName()
: Returns the name of the current thread.setPriority(int priority)
: Sets the priority of the thread.
Example of sleep()
and join()
:
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000); // Sleep for 2 seconds
System.out.println(Thread.currentThread().getName() + " finished sleeping.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadMethodsExample {
public static void main(String[] args) throws InterruptedException {
MyThread thread1 = new MyThread();
thread1.start();
thread1.join(); // Main thread waits for thread1 to finish
System.out.println("Main thread resumes after thread1 finishes.");
}
}
Thread Synchronization
Thread synchronization is a technique to ensure that only one thread can access a resource at a time, preventing race conditions.
In Java, you can synchronize a method or block of code using the synchronized
keyword.
Example: Synchronizing a Method
class Counter {
private int count = 0;
// Synchronized method to ensure thread-safe increment
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.getCount()); // Output: 2000
}
}
Explanation:
synchronized
keyword: Theincrement()
method is synchronized, meaning only one thread can execute it at a time, ensuring thread safety.
Thread Pool (Executor Framework)
Creating and managing threads manually can be cumbersome and inefficient, especially in applications with many threads. Java provides the Executor Framework for managing a pool of threads, which can help reuse threads and reduce overhead.
Example of Using ExecutorService
:
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2); // Create a thread pool with 2 threads
Runnable task1 = () -> {
System.out.println(Thread.currentThread().getName() + " is executing task1");
};
Runnable task2 = () -> {
System.out.println(Thread.currentThread().getName() + " is executing task2");
};
executor.submit(task1);
executor.submit(task2);
executor.shutdown(); // Initiates an orderly shutdown
}
}
Explanation:
ExecutorService
: Manages a pool of threads to execute tasks.submit()
: Submits tasks for execution.shutdown()
: Initiates an orderly shutdown of the executor.
Concurrency Utilities
Java provides several classes in the java.util.concurrent
package to help manage concurrency, such as:
- CountDownLatch: Allows one or more threads to wait until a set of operations is completed.
- CyclicBarrier: A synchronization barrier that enables multiple threads to wait for each other to reach a common point.
- Semaphore: Controls access to a particular resource by multiple threads.
- ReentrantLock: An implementation of a lock that allows threads to lock and unlock resources.
- Atomic Variables: Classes like
AtomicInteger
,AtomicLong
, andAtomicReference
provide thread-safe operations on variables.
Example of Using CountDownLatch
:
import java.util.concurrent.*;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is working");
Thread.sleep(1000);
latch.countDown(); // Decrements the latch count
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
latch.await(); // Main thread will wait until the latch count reaches 0
System.out.println("All threads finished");
}
}
Conclusion
Multithreading in Java is a powerful feature that enables efficient execution of concurrent tasks. Key concepts and features include:
- Creating threads: Using
Thread
class orRunnable
interface. - Thread lifecycle: The various states a thread goes through.
- Synchronization: Ensuring thread safety when accessing shared resources.
- Executor framework: Efficient thread management with a thread pool.
- Concurrency utilities: Tools like
CountDownLatch
,Semaphore
, andReentrantLock
for advanced concurrency control.
By understanding and leveraging multithreading, you can significantly improve the performance and responsiveness of Java applications.