Threads in Java
A thread is a lightweight, independent path of execution within a program.
Java supports multithreading, allowing multiple threads to run concurrently within a single program, making it possible to perform many tasks at once.
In Java, threads are instances of the Thread class or objects that implement the Runnable interface.
Importance:
Improved Performance: Especially on multi-core processors, tasks can be performed simultaneously.
Responsive Applications: Allows background tasks like file downloading or data processing while keeping the main application responsive.
Resource Sharing: Threads within the same process share resources, which can lead to more efficient memory and resource usage.
Creating Threads:
There are several ways to create threads in Java:
Extending the Thread class
Implementing the Runnable interface
Using Lambda Expressions for Anonymous Threads
Extending the Thread Class:
To create a thread by extending the Thread class, you create a new class that extends Thread and override its run() method.
Example:
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
Output
Thread-1 - Task is running: 0
Thread-1 - Task is running: 1
Thread-1 - Task is running: 2
Thread-1 - Task is running: 3
Thread-1 - Task is running: 4
Thread-0 - Task is running: 0
Thread-0 - Task is running: 1
Thread-0 - Task is running: 2
Thread-0 - Task is running: 3
Thread-0 - Task is running: 4
Implementing the Runnable Interface:
To create a thread by implementing the Runnable interface, you create a new class that implements Runnable and pass an instance of this class to a Thread object.
Example:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
}
}
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.start();
thread2.start();
}
}
Output
Thread-0 - Task is running: 0
Thread-0 - Task is running: 1
Thread-0 - Task is running: 2
Thread-0 - Task is running: 3
Thread-0 - Task is running: 4
Thread-1 - Task is running: 0
Thread-1 - Task is running: 1
Thread-1 - Task is running: 2
Thread-1 - Task is running: 3
Thread-1 - Task is running: 4
Anonymous Threads (Lambda Expression):
Java 8 introduced lambda expressions, which provide a concise way to create anonymous threads.
Example:
public class Main {
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
}
}
Output
Thread-1 - Task is running: 0
Thread-1 - Task is running: 1
Thread-1 - Task is running: 2
Thread-1 - Task is running: 3
Thread-1 - Task is running: 4
Thread-0 - Task is running: 0
Thread-0 - Task is running: 1
Thread-0 - Task is running: 2
Thread-0 - Task is running: 3
Thread-0 - Task is running: 4
Thread States and Lifecycle:
Thread States:
Threads in Java can be in different states:
New: A thread that's been created but not yet started.
Runnable: A thread that's ready to run is moved to the runnable state.
Blocked: A thread that is blocked, waiting for a monitor lock.
Waiting: A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
Timed Waiting: A thread that is waiting for another thread to perform a particular action within a stipulated amount of time.
Terminated: A thread that has exited is in this state.
Thread Lifecycle:
The lifecycle of a thread involves the following states:
New: When a thread is created.
Runnable: When the
start
method is called.Blocked, Waiting, Timed Waiting: In various scenarios depending on the thread's actions.
Terminated: When the
run
method completes or an unhandled exception occurs.
Thread Methods:
start(): Begins the execution of a thread.
run(): Contains the code that constitutes the new thread.
sleep(milliseconds): Causes the current thread to pause for a specified period.
join(): Waits for the thread to die.
yield(): Causes the currently executing thread to pause and allow other threads to execute.
Thread Synchronization:Synchronized method:
When multiple threads access shared resources, data inconsistency and corruption can occur. Synchronization helps to ensure that only one thread can access a resource at a time.
Example:
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SynchronizationExample {
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("Count: " + counter.getCount());
}
}
Output:
Count: 2000
In this example, the increment()
method is synchronized to ensure that only one thread can execute it at a time, preventing data corruption.
Thread Interruption:
Thread interruption is a way to signal a thread that it should stop its current work and do something else. This is useful for tasks that may need to be canceled or stopped before completion.
Example:
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("Task is running: " + i);
Thread.sleep(500); // Simulate some work
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
});
thread.start();
try {
Thread.sleep(2000); // Main thread sleeps for 2 seconds
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); // Interrupt the worker thread
}
}
Output
Task is running: 0
Task is running: 1
Task is running: 2
Task is running: 3
Thread was interrupted
Thread Safety:
Immutable Objects: Immutable objects are inherently thread-safe as their state cannot be changed after creation.
Local Variables: Variables local to a method are thread-safe as each thread gets its own copy.
Synchronized Methods/Blocks: Use synchronized methods or blocks to control access to shared resources.
Thread Pools:
Thread pools manage a pool of worker threads and can be used to execute tasks without the overhead of creating new threads for each task. This is managed by the ExecutorService.
Example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Task is running: " + i);
}
};
executorService.submit(task);
executorService.submit(task);
executorService.submit(task);
executorService.shutdown();
}
}
Output
pool-1-thread-3 - Task is running: 0
pool-1-thread-3 - Task is running: 1
pool-1-thread-3 - Task is running: 2
pool-1-thread-3 - Task is running: 3
pool-1-thread-1 - Task is running: 0
pool-1-thread-1 - Task is running: 1
pool-1-thread-1 - Task is running: 2
pool-1-thread-1 - Task is running: 3
pool-1-thread-1 - Task is running: 4
pool-1-thread-2 - Task is running: 0
pool-1-thread-2 - Task is running: 1
pool-1-thread-2 - Task is running: 2
pool-1-thread-2 - Task is running: 3
pool-1-thread-2 - Task is running: 4
pool-1-thread-3 - Task is running: 4
Summary:
Threads: Lightweight units of execution within a program.
Creation: Extend Thread, implement Runnable, or use lambda expressions for anonymous threads.
Thread States: New, Runnable, Blocked, Waiting, Timed Waiting, Terminated.
Thread Interruption: Provides a mechanism to signal a thread to stop its work.
Synchronization: Ensures safe access to shared resources.
Thread Safety: Achieved through immutable objects, local variables, and synchronized methods/blocks.
Thread Pools: Efficiently manage multiple threads with ExecutorService.
Understanding threads and concurrency in Java is crucial for writing efficient, responsive, and high-performance applications.