A thread is a lightweight subprocess, the smallest unit of processing that can be scheduled by an operating system. In Java, threads allow concurrent execution of code, improving application performance and responsiveness. Each thread has its own execution context, which includes its own call stack and local variables, while sharing memory space with other threads.
A thread in Java undergoes several states during its lifecycle:
notify()
).There are two primary ways to create threads in Java:
Thread
class: By creating a subclass of the Thread
class and overriding its run()
method.Runnable
interface: By creating a class that implements the Runnable
interface and defining the run()
method.To create a thread by extending the Thread
class, follow these steps:
Thread
.run()
method to define the code that will be executed by the thread.start()
method to initiate the thread.
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
try {
Thread.sleep(500); // Sleep for 500 milliseconds
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start(); // Start thread1
thread2.start(); // Start thread2
}
}
MyThread
class extends the Thread
class and overrides the run()
method, which contains the code to be executed by the thread.start()
method is called on each thread instance. This method invokes the run()
method in a new call stack.The second approach to creating threads in Java is by implementing the Runnable
interface. This is often preferred because it allows for better separation of concerns and can be used with thread pools and other concurrency utilities.
Runnable
interface.run()
method to define the code that will be executed by the thread.Thread
object by passing an instance of the Runnable
class to the Thread
constructor.start()
method on the Thread
object.
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
try {
Thread.sleep(500); // Sleep for 500 milliseconds
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
}
}
}
public class RunnableExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread1.start(); // Start thread1
thread2.start(); // Start thread2
}
}
MyRunnable
class implements the Runnable
interface and overrides the run()
method.Thread
objects are created, each taking the same MyRunnable
instance.start()
method is called on both thread instances, executing the run()
method concurrently.Java provides several methods in the Thread
class for managing threads. Some commonly used methods include:
start()
: Starts the execution of the thread.run()
: Contains the code to be executed by the thread.sleep(long millis)
: Pauses the current thread for a specified time.join()
: Waits for the thread to finish execution before proceeding.interrupt()
: Interrupts a thread, setting its interrupt flag.isAlive()
: Checks if the thread is still running.
class CounterThread extends Thread {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " - Count: " + count);
try {
Thread.sleep(500); // Sleep for 500 milliseconds
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
}
}
public int getCount() {
return count;
}
}
public class ThreadMethodExample {
public static void main(String[] args) {
CounterThread thread1 = new CounterThread();
CounterThread thread2 = new CounterThread();
thread1.start(); // Start thread1
thread2.start(); // Start thread2
try {
thread1.join(); // Wait for thread1 to finish
thread2.join(); // Wait for thread2 to finish
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Both threads have finished execution.");
}
}
CounterThread
class extends Thread
and maintains a count
variable.run()
method increments the count and prints its value, then sleeps for 500 milliseconds.join()
method is used to ensure that the main thread waits for both CounterThread
instances to finish before printing the final message.When multiple threads access shared resources, synchronization is necessary to avoid data inconsistency and race conditions. Java provides synchronization mechanisms to control the access of multiple threads to shared resources.
A synchronized method allows only one thread to execute it at a time, ensuring that shared resources are accessed in a thread-safe manner.
class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class SynchronizedMethodExample {
public static void main(String[] args) {
SynchronizedCounter counter = new SynchronizedCounter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
SynchronizedCounter
class has a synchronized method increment()
that safely increments the count
.Synchronized blocks provide finer control over synchronization by allowing only specific sections of code to be synchronized.
class SynchronizedBlockCounter {
private int count = 0;
public void increment() {
synchronized (this) { // Synchronized block
count++;
}
}
public int getCount() {
return count;
}
}
public class SynchronizedBlockExample {
public static void main(String[] args) {
SynchronizedBlockCounter counter = new SynchronizedBlockCounter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
increment()
method contains a synchronized block that ensures only one thread can increment the count at a time.