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 your Runnable class as an argument to the constructor.
  • Call the start() method on the Thread 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 using notify() or notifyAll().
  • 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 as suspend().
  • 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’s run() method.
  • Exiting the run() method: If the thread is checking the interrupt flag regularly, it can exit the run() 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:

  • byteByte
  • shortShort
  • intInteger
  • longLong
  • floatFloat
  • doubleDouble


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