Introduction
The Thread
class in Java is a part of the java.lang
package and provides the facility to create and manage threads. A thread is a lightweight subprocess, the smallest unit of processing. Multithreading in Java is a process of executing multiple threads simultaneously to maximize CPU utilization and improve the performance of applications.
Key Points:
- Part of
java.lang
Package: TheThread
class is included in thejava.lang
package, which is automatically imported. - Concurrent Execution: Allows concurrent execution of code.
- Lightweight Process: A thread is lighter than a process and shares the same memory space.
- Thread Lifecycle Management: Provides methods to manage the lifecycle of a thread.
- Synchronization Support: Offers built-in support for synchronization.
Table of Contents
- Creating a Thread
- Thread Lifecycle
- Thread States
- Thread Methods with Examples
- Thread Synchronization
- Example: Comprehensive Usage of Thread Class
- Best Practices
- Real-World Analogy
- Conclusion
1. Creating a Thread
Extending the Thread
Class
You can create a thread by extending the Thread
class and overriding its run()
method.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Start the thread
}
}
Implementing the Runnable
Interface
You can also create a thread by implementing the Runnable
interface and passing an instance of your class to a Thread
object.
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running...");
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start(); // Start the thread
}
}
2. Thread Lifecycle
A thread in Java goes through several stages in its lifecycle:
- New: A thread that has been created but not yet started.
- Runnable: A thread that is ready to run and waiting for CPU time.
- Blocked: A thread that is waiting for a monitor lock to enter or re-enter a synchronized block/method.
- Waiting: A thread that is waiting indefinitely for another thread to perform a particular action.
- Timed Waiting: A thread that is waiting for another thread to perform a particular action for a specified waiting time.
- Terminated: A thread that has exited.
3. Thread States
Example:
public class ThreadStatesExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000); // Timed waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread is running...");
});
System.out.println("State: " + t1.getState()); // NEW
t1.start();
System.out.println("State: " + t1.getState()); // RUNNABLE
try {
Thread.sleep(500);
System.out.println("State: " + t1.getState()); // TIMED_WAITING
t1.join();
System.out.println("State: " + t1.getState()); // TERMINATED
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. Thread() Methods with Examples
4.1. Creating a Thread
Creating a thread by extending the Thread
class and implementing the Runnable
interface.
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Start the thread
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start(); // Start the thread
}
}
4.2. Starting a Thread
The start()
method is used to begin the execution of a thread. It invokes the run()
method.
Example:
public class StartThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread started");
});
thread.start(); // Start the thread
}
}
4.3. Sleep Method
The sleep(long millis)
method pauses the execution of the current thread for the specified number of milliseconds.
Example:
public class SleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
try {
Thread.sleep(1000); // Sleep for 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
4.4. Join Method
The join()
method allows one thread to wait for the completion of another.
Example:
public class JoinExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 1: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
try {
t1.join(); // Wait for t1 to finish
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 5; i++) {
System.out.println("Thread 2: " + i);
}
});
t1.start();
t2.start();
}
}
4.5. Interrupt Method
The interrupt()
method interrupts a thread, causing it to exit from a blocking state like sleep.
Example:
public class InterruptExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
});
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); // Interrupt the thread
}
}
4.6. Checking if a Thread is Alive
The isAlive()
method checks if a thread is still running.
Example:
public class IsAliveExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
while (thread.isAlive()) {
System.out.println("Thread is alive");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread has finished execution");
}
}
4.7. Thread Priorities
Each thread has a priority, which helps the thread scheduler determine the order of thread execution. Use the setPriority(int priority)
and getPriority()
methods to set and get the thread’s priority.
Example:
public class ThreadPriorityExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("Thread t1 is running..."));
Thread t2 = new Thread(() -> System.out.println("Thread t2 is running..."));
t1.setPriority(Thread.MIN_PRIORITY); // Set priority to 1
t2.setPriority(Thread.MAX_PRIORITY); // Set priority to 10
t1.start();
t2.start();
}
}
4.8. Setting and Getting Thread Name
You can set and get the name of a thread using the setName(String name)
and getName()
methods.
Example:
public class ThreadNameExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> System.out.println("Thread is running..."));
thread.setName("MyThread");
thread.start();
System.out.println("Thread name: " + thread.getName());
}
}
5. Thread Synchronization
Synchronization is used to control the access of multiple threads to shared resources. This ensures that only one thread can access the resource at a time, preventing data inconsistency.
Synchronized Method
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizationExample {
public static void main(String[] args) {
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();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + counter.getCount()); // Expected output: 2000
}
}
Synchronized Block
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
6. Example: Comprehensive Usage of Thread Class
Example: Multi-Threaded Sum Calculation
class SumTask extends Thread {
private int[] arr;
private int start;
private int end;
private int result;
public SumTask(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
public int getResult() {
return result;
}
@Override
public void run() {
result = 0;
for (int i = start; i < end; i++) {
result += arr[i];
}
}
}
public class MultiThreadingExample {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int mid = arr.length / 2;
SumTask task1 = new SumTask(arr, 0, mid);
SumTask task2 = new SumTask(arr, mid, arr.length);
task1.start();
task2.start();
try {
task1.join();
task2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
int totalSum = task1.getResult() + task2.getResult();
System.out.println("Total Sum: " + totalSum);
}
}
Output:
Total Sum: 55
7. Best Practices
- Avoid Busy Waiting: Use proper synchronization techniques (
wait()
,notify()
,notifyAll()
) instead of busy-waiting loops. - Use Thread Pools: For managing multiple short-lived tasks, use thread pools provided by the
ExecutorService
to efficiently manage resources. - Handle InterruptedException: Properly handle
InterruptedException
to ensure threads can be interrupted gracefully. - Minimize Synchronized Sections: Keep synchronized sections as short as possible to avoid performance bottlenecks.
- Avoid Deadlocks: Design your synchronization strategy to avoid deadlocks by using techniques like lock ordering.
- Use Atomic Variables: Use atomic variables (
AtomicInteger
,AtomicBoolean
) for simple thread-safe operations. - Prefer High-Level Concurrency Utilities: Use high-level concurrency utilities from the
java.util.concurrent
package for complex synchronization and concurrency tasks.
8. Real-World Analogy
Consider a bank where multiple customers (threads) perform transactions (tasks) simultaneously. The bank system must ensure that:
- Adding Transactions: Customers can deposit and withdraw money concurrently.
- Handling Synchronization: Ensure that two customers do not withdraw money from the same account at the same time to prevent overdrafts.
- Managing Multiple Requests: The bank system uses thread pools to handle multiple customer requests efficiently.
- Ensuring Data Consistency: The system ensures data consistency by synchronizing access to shared resources.
9. Conclusion
The Thread
class in Java is a fundamental building block for creating and managing threads, enabling concurrent execution of code. By understanding how to create, manage, and synchronize threads, you can effectively utilize multithreading in your Java applications. Following best practices will help you write efficient, thread-safe, and maintainable code.