Multithreading Programming in java

Multithreading in Java refers to the concurrent execution of multiple threads within a single program. A thread is a lightweight, independent unit of execution, and multithreading allows multiple threads to run concurrently, sharing the same resources like CPU and memory. Java provides built-in support for multithreading through the Thread class and the java.util.concurrent package.

Creating Threads:

1. Extending Thread Class:

class MyThread extends Thread {
    public void run() {
        // Code to be executed in the new thread
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

public class MultithreadingExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        // Start threads
        thread1.start();
        thread2.start();

        // Code in the main thread
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

2. Implementing Runnable Interface:

class MyRunnable implements Runnable {
    public void run() {
        // Code to be executed in the new thread
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

public class MultithreadingExample {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        // Create threads using the Runnable implementation
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);

        // Start threads
        thread1.start();
        thread2.start();

        // Code in the main thread
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

Thread States and Lifecycle:

A thread in Java can be in one of the following states:

  • New: When the thread is created but not yet started.
  • Runnable: When the thread is ready to run but is waiting for CPU time.
  • Blocked: When the thread is waiting for a monitor lock to enter a synchronized block/method.
  • Waiting: When the thread is waiting indefinitely for another thread to perform a particular action.
  • Timed Waiting: When the thread is waiting for another thread to perform a particular action for a specified amount of time.
  • Terminated: When the thread has completed its execution.

Synchronization:

Synchronization is crucial in multithreading to prevent race conditions and data inconsistencies. The synchronized keyword is used to define critical sections, ensuring that only one thread can access shared resources at a time.

class SharedResource {
    synchronized void synchronizedMethod() {
        // Critical section code
    }
}

Thread Priorities:

Thread priorities (ranging from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY) indicate the importance of a thread. However, they are platform-dependent and do not guarantee precise behavior.

Thread thread = new Thread();
thread.setPriority(Thread.NORM_PRIORITY); // Set thread priority
int priority = thread.getPriority(); // Get thread priority

Joining Threads:

The join() method is used to wait for a thread to complete its execution before proceeding with the current thread.

Thread thread1 = new Thread();
Thread thread2 = new Thread();

thread1.start();
thread2.start();

try {
    thread1.join(); // Wait for thread1 to finish
    thread2.join(); // Wait for thread2 to finish
} catch (InterruptedException e) {
    e.printStackTrace();
}

Thread Safety and Deadlock:

  • Ensure thread safety to prevent multiple threads from accessing shared resources concurrently.
  • Be cautious about deadlock situations where threads are blocked indefinitely waiting for each other.

Executor Framework:

The java.util.concurrent package provides higher-level concurrency utilities, including the ExecutorService, ThreadPoolExecutor, and Future classes, for efficient multithreading.

ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.submit(() -> {
    // Thread logic
});

Multithreading in Java requires careful synchronization and coordination to avoid common pitfalls such as race conditions and deadlocks. The java.util.concurrent package provides advanced tools for managing concurrency efficiently.

A multithreading programming program contains two or more parts that can run concurrently. each part of such a program is called a thread, and each thread defines a separate path of execution

multithreading enables you to write very efficient programs that make maximum use of the CPU, because idle time can be kept to a minimum

The Java Thread Model

The Java run-time system depends on threads for many things, and all the class libraries are designed with multithreading in mind. In fact, Java uses threads to enable the entire environment to be asynchronous. This helps reduce inefficiency by preventing the waste of CPU cycles

The benefit of Java’s multithreading is that the main loop/polling mechanism is eliminated. One thread can pause without stopping other parts of your program. For example, the idle time created when a thread reads data from a network or waits for user input can be utilized elsewhere. Multithreading allows animation loops to sleep for a second between each frame without causing the whole system to pause. When a thread blocks in a Java program, only the single thread that is blocked pauses. All other threads continue to run

Thread Priorities

Java assigns to each thread a priority that determines how that thread should be treated with respect to the others. Thread priorities are integers that specify the relative priority of one thread to another. As an absolute value, a priority is meaningless; a higher-priority thread doesn’t run any faster than a lower-priority thread if it is the only thread running. Instead, a thread’s priority is used to decide when to switch from one running thread to the next. This is called a context switch. The rules that determine when a context switch takes place are simple

  • A thread can voluntarily relinquish control. This is done by explicitly yielding, sleeping, or blocking on pending I/O. In this scenario, all other threads are examined, and the highest-priority thread that is ready to run is given the CPU.
  • A thread can be preempted by a higher-priority thread. In this case, a lower-priority thread that does not yield the processor is simply preempted—no matter what it is doing— by a higher-priority thread. Basically, as soon as a higher-priority thread wants to run, it does. This is called preemptive multitasking

Synchronization

Because multithreading introduces an asynchronous behavior to your programs, there must be a way for you to enforce synchronicity when you need it. For example, if we want two threads to communicate and share a complicated data structure, such as a linked list, we need some way to ensure that they don’t conflict with each other. That is, we must prevent one thread from writing data while another thread is in the middle of reading it. For this purpose, Java implements an elegant twist on an age-old model of interprocess synchronization: the monitor. The monitor is a control mechanism first defined by C.A.R. Hoare. we can think of a monitor as a very small box that can hold only one thread. Once a thread enters a monitor, all other threads must wait until that thread exits the monitor. In this way, a monitor can be used to protect a shared asset from being manipulated by more than one thread at a time

Messaging

After we divide your program into separate threads, we need to define how they will communicate with each other. When programming with most other languages, you must depend on the operating system to establish communication between threads. This, of course, adds overhead. By contrast, Java provides a clean, low-cost way for two or more threads to talk to each other, via calls to predefined methods that all objects have. Java’s messaging system allows a thread to enter a synchronized method on an object, and then wait there until some other thread explicitly notifies it to come out

The Thread Class and the Runnable Interface

Java’s multithreading system is built upon the Thread class, its methods, and its companion interface, Runnable. Thread encapsulates a thread of execution

To create a new thread, your program will either extend Thread or implement the Runnable interface

Table of Thread class method

MethodMeaning
getNameObtain a thread’s name
getPriorityObtain a thread’s priority
isAliveDetermine if a thread is still running.
joinWait for a thread to terminate.
runEntry point for the thread.
sleepSuspend a thread for a period of time.
startStart a thread by calling its run method.

The Main Thread

When a Java program starts up, one thread begins running immediately. This is usually called the main thread of your program, because it is the one that is executed when your program begins. The main thread is important for two reasons:

  • It is the thread from which other “child” threads will be spawned
  • Often, it must be the last thread to finish execution because it performs various shutdown actions.

Although the main thread is created automatically when your program is started, it can be controlled through a Thread object. To do so, we must obtain a reference to it by calling the method currentThread( ), which is a public static member of Thread.

static Thread currentThread( )

Controlling the main Thread

class CurrentThreadDemo {
public static void main(String args[]) {
Thread t = Thread.currentThread();
System.out.println("Current thread: " + t);
// change the name of the thread
t.setName("My Thread");
System.out.println("After name change: " + t);
try {
for(int n = 5; n > 0; n--) {
System.out.println(n);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("Main thread interrupted");
}
}
}

Current thread: Thread[main,5,main]
After name change: Thread[My Thread,5,main]
5
4
3
2
1

Leave a Comment