Java Multithreading: Concepts, Synchronization, and Communication
What is a Thread?
In computer programming, a thread is a fundamental unit of execution within a process. It’s a lightweight subprocess that shares the same memory space as its parent process. Multiple threads can run concurrently within a single process, allowing for parallel execution of tasks.
Ways to Create Threads in Java
Implementing the Runnable Interface:
- Create a class that implements the
Runnable
interface. - Implement the
run()
method, which contains the code to be executed by the thread. - Instantiate a
Thread
object, passing an instance of yourRunnable
class as an argument to the constructor. - Call the
start()
method on theThread
object to begin its execution.
Example:
class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread is running!");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
Extending the Thread Class:
- Create a class that extends the
Thread
class. - Override the
run()
method to define the code to be executed by the thread. - Instantiate an object of your custom class.
- Call the
start()
method on the thread object.
Example:
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running!");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
Need for Synchronization
Synchronization is a mechanism that ensures that multiple threads can work together safely without interfering with each other when accessing shared resources. When multiple threads attempt to access or modify a shared resource concurrently, it can lead to inconsistent or unpredictable results, often referred to as race conditions. Synchronization prevents this by ensuring that only one thread can access a shared resource at a time.
Example with Synchronization
class Counter {
private int count = 0;
public synchronized void increment() { // Synchronize the method
count++;
}
public int getCount() {
return count;
}
}
public class Main {
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());
}
}
What is Multithreading?
Multithreading is a technique that allows multiple threads of execution to run concurrently within a single process. Each thread is a separate flow of control that can execute independently, sharing the same memory space as the parent process. This enables parallel execution of tasks, improving performance and responsiveness, especially in applications that need to handle multiple tasks simultaneously.
Java Program to Create Multiple Threads
public class MultithreadingExample {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
Thread thread3 = new Thread(new MyRunnable());
thread1.start();
thread2.start();
thread3.start();
}
static class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Thread " + Thread.currentThread().getName() + ": " + i);
}
}
}
}
Inter-Thread Communication (ITC)
Inter-thread communication (ITC) is essential in multithreaded applications to coordinate the activities of multiple threads. Java provides several mechanisms for ITC, including:
1. wait()
, notify()
, and notifyAll()
Methods:
wait()
: Causes the current thread to wait until another thread signals it usingnotify()
ornotifyAll()
.notify()
: Wakes up a single thread that is waiting on the object’s monitor.notifyAll()
: Wakes up all threads that are waiting on the object’s monitor.
Example:
public class ProducerConsumer {
private static final int MAX_SIZE = 10;
private int[] buffer = new int[MAX_SIZE];
private int count = 0;
private int in = 0;
private int out = 0;
public synchronized void put(int value) throws InterruptedException {
while (count == MAX_SIZE) {
wait();
}
buffer[in] = value;
in = (in + 1) % MAX_SIZE;
count++;
notify();
}
public synchronized int get() throws InterruptedException {
while (count == 0) {
wait();
}
int value = buffer[out];
out = (out + 1) % MAX_SIZE;
count--;
notify();
return value;
}
}
Autoboxing and Unboxing
Autoboxing: Automatically converts primitive data types (like int
, double
, boolean
, etc.) into their corresponding wrapper classes (like Integer
, Double
, Boolean
, etc.). This conversion happens implicitly when a primitive value is used in a context that requires a wrapper object.
Unboxing: Automatically converts wrapper objects back into their corresponding primitive data types. This conversion happens implicitly when a wrapper object is used in a context that requires a primitive value.
Example:
int x = 10;
Integer y = x; // Autoboxing: int to Integer
int z = y; // Unboxing: Integer to int
boolean b = true;
Boolean c = b; // Autoboxing: boolean to Boolean
boolean d = c; // Unboxing: Boolean to boolean
Thread Priority
In Java, thread priority is a numerical value that indicates the relative importance of a thread to the operating system’s scheduler. Higher-priority threads are generally given preference for CPU time over lower-priority threads. However, it’s important to note that thread priority is not a guarantee of execution order, as other factors like system load and thread scheduling algorithms can influence the actual execution sequence.
Assigning Thread Priority: Use the setPriority()
method of the Thread
class to set the priority of a thread. The priority is an integer value between Thread.MIN_PRIORITY
and Thread.MAX_PRIORITY
. The default priority of a thread is Thread.NORM_PRIORITY
.
Example:
Thread thread = new Thread(new MyRunnable());
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
Getting Thread Priority: Use the getPriority()
method of the Thread
class to retrieve the priority of a thread.
Example:
int priority = thread.getPriority();
System.out.println("Thread priority: " + priority);
values()
and valueOf()
Methods in Enumeration
values()
Method:
- Returns an array of all the enum constants defined within an enumeration type.
- The array is ordered in the same sequence as the constants were declared.
Example:
enum Color {
RED, GREEN, BLUE;
}
Color[] colors = Color.values();
for (Color color : colors) {
System.out.println(color);
}
valueOf()
Method:
- Returns the enum constant corresponding to the specified string name.
- The string name must match the name of an enum constant exactly (case-sensitive).
Example:
Color red = Color.valueOf("RED");
System.out.println(red); // Output: RED
Suspending, Resuming, and Stopping Threads
Suspending Threads:
- Deprecated: The
Thread.suspend()
method is deprecated in Java because it can lead to deadlocks. - Alternative: Use a volatile boolean flag to signal the thread to pause its execution.
Resuming Threads:
- Deprecated: The
Thread.resume()
method is also deprecated for the same reasons assuspend()
. - Alternative: Modify the volatile boolean flag to allow the thread to continue execution.
Stopping Threads:
- Interrupting: Use the
interrupt()
method to interrupt a thread. This sets the thread’s interrupt flag, which can be checked within the thread’srun()
method. - Exiting the
run()
method: If the thread is checking the interrupt flag regularly, it can exit therun()
method when the flag is set, effectively stopping the thread.
Example:
public class SuspendResumeStopExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
e.printStackTrace();
}
// Suspend the thread (using a flag)
thread.suspend();
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
e.printStackTrace();
}
// Resume the thread (using a flag)
thread.resume();
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
e.printStackTrace();
}
// Stop the thread (interrupting it)
thread.interrupt();
}
}
Enumeration
Enumeration in Java is a special type of class that defines a fixed set of constants. It’s used to represent a group of related values that are mutually exclusive.
Syntax:
enum Color { RED, GREEN, BLUE; }
Wrapper Classes
In Java, wrapper classes are classes that provide object representations for primitive data types. They are used to treat primitive values as objects, allowing them to be used in situations where objects are required, such as collections or generics.
Primitive Type – Wrapper Class:
byte
–Byte
short
–Short
int
–Integer
long
–Long
float
–Float
double
–Double
ordinal()
and compareTo()
ordinal()
Method:
Returns the ordinal position of an enum constant within its declaration order. The ordinal position starts from 0 for the first constant, 1 for the second, and so on.
Example:
enum Color {
RED, GREEN, BLUE;
}
Color red = Color.RED;
int ordinal = red.ordinal();
System.out.println("Ordinal of RED: " + ordinal); // Output: 0
compareTo()
Method:
Compares this enum constant to the specified object. If the specified object is an enum constant of the same type, the method returns an integer indicating the relative order of the two constants based on their ordinal positions. If the specified object is not an enum constant of the same type, the method throws a ClassCastException
.
Example:
Color red = Color.RED;
Color green = Color.GREEN;
int comparison = red.compareTo(green);
System.out.println("Comparison result: " + comparison); // Output: -1