Java — Multithreading

Muhammed Öner
7 min readDec 24, 2021

--

Multithreading is a model of program execution that allows for multiple threads to be created within a process, executing independently but concurrently sharing process resources. Depending on the hardware, threads can run fully parallel if they are distributed to their own CPU core.

Multi-threading enables you to write in a way where multiple activities can proceed concurrently in the same program.

Advantages of Multithreading Application

  • Multithreading saves time as you can perform multiple operations together.
  • The threads are independent, so it does not block the user to perform multiple operations at the same time and also, if an exception occurs in a single thread, it does not affect other threads.
  • You can perform many operations together, so it saves time.

A thread is a lightweight subprocess, the smallest unit of processing. It is a separate path of execution.

Life Cycle of a Thread

A thread goes through various stages in its life cycle. The following diagram shows the complete life cycle of a thread.

Following are the stages of the life cycle

  • New − A new thread begins its life cycle in the new state. It remains in this state until the program starts the thread. It is also referred to as a born thread.
  • Runnable − After a newly born thread is started, the thread becomes runnable. A thread in this state is considered to be executing its task.
  • Waiting − Sometimes, a thread transitions to the waiting state while the thread waits for another thread to perform a task. A thread transitions back to the runnable state only when another thread signals the waiting thread to continue executing.
  • Timed Waiting − A runnable thread can enter the timed waiting state for a specified interval of time. A thread in this state transitions back to the runnable state when that time interval expires or when the event it is waiting for occurs.
  • Terminated (Dead) − A runnable thread enters the terminated state when it completes its task or otherwise terminates.

Thread Priorities
Every Java thread has a priority that helps the operating system determine the order in which threads are scheduled.

Java thread priorities are in the range between MIN_PRIORITY (a constant of 1) and MAX_PRIORITY (a constant of 10). By default, every thread is given priority NORM_PRIORITY (a constant of 5).

Threads with higher priority are more important to a program and should be allocated processor time before lower-priority threads. However, thread priorities cannot guarantee the order in which threads execute and are very much platform dependent.

Create a Thread by Implementing a Runn

There are two ways to write a multithreading application.

  1. Extending the Thread class
class ThreadDemo extends Thread {
public void run() {
// code
}
}

2. Implementing the Runnable Interface

class RunnableDemo implements Runnable {
public void run() {
// code
}
}

Examples

  1. Thread Class Extending Thread.class
public class ThreadDemo extends Thread {

public ThreadDemo(String name) {
super(name);
}

public void run() {

System.out.println(getName() + " is starting.");

for (int i = 0; i < 10; i++) {
try {
Thread.sleep(400);
System.out.println("In " + getName() + " count is " + i);
} catch (InterruptedException e) {
System.out.println(getName() + " is interrupted");
}
}

System.out.println(getName() + " is terminating");
}
}

Execution

public class Main {

public static void main(String[] args) {

System.out.println("Main thread is starting");

ThreadDemo myThread = new ThreadDemo("child1");
ThreadDemo myThread2 = new ThreadDemo("child2");
ThreadDemo myThread3 = new ThreadDemo("child3");

myThread.start();
myThread2.start();
myThread3.start();

for (int i = 0; i < 10; i++) {
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("Main thread is interrupted");
}
}

System.out.println("Main thread is terminating");
}

}

2. Thread Class Implementing Runnable Interface

public class ThreadDemo implements Runnable {

private String threadDemo;

public ThreadDemo(String name) {
this.threadDemo = name;
}

public void run() {
System.out.println(threadDemo + " is starting.");

for (int i = 0; i < 10; i++) {
try {
Thread.sleep(400);
System.out.println("In " + threadDemo + " count is " + i);
} catch (InterruptedException e) {
System.out.println(threadDemo + " is interrupted");
}
}

System.out.println(threadDemo + " is terminating");
}
}

Execution

public class Main {

public static void main(String[] args) {

System.out.println("Main thread is starting");

ThreadDemo task = new ThreadDemo("child1");
Thread thread = new Thread(task);

thread.start();

for (int i = 0; i < 10; i++) {
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("Main thread is interrupted");
}
}
System.out.println("Main thread is terminating");
}

}

Thread Priorities

Every Java thread has a priority that helps the operating system determine the order in which threads are scheduled.

Java thread priorities are in the following ranges;

MIN_PRIORITY = 1

MAX_PRIORITY = 10

NORM_PRIORITY = 5

Threads with higher priority are more important to a program and should be allocated processor time before lower-priority threads. However, thread priorities cannot guarantee the order in which threads execute and are very much platform dependent.

public class Main {

public static void main(String[] args) {
ThreadDemo myThread = new ThreadDemo("test1", Thread.MAX_PRIORITY);
ThreadDemo myThread2 = new ThreadDemo("test2", Thread.MIN_PRIORITY);
ThreadDemo myThread3 = new ThreadDemo("test3", Thread.NORM_PRIORITY);

myThread.start();
myThread2.start();
myThread3.start();
}

}
OUTPUTchild1 is starting.
child3 is starting.
child2 is starting.
In child3 count is 0
In child1 count is 0
In child2 count is 0
In child2 count is 1
In child1 count is 1
In child3 count is 1
In child1 count is 2
In child3 count is 2
In child2 count is 2
In child1 count is 3
In child2 count is 3
In child3 count is 3
In child3 count is 4
In child2 count is 4
In child1 count is 4
In child3 count is 5
In child2 count is 5
In child1 count is 5
In child3 count is 6
In child1 count is 6
In child2 count is 6
In child2 count is 7
In child1 count is 7
In child3 count is 7
In child3 count is 8
In child1 count is 8
In child2 count is 8
In child3 count is 9
child3 is terminating
In child2 count is 9
child2 is terminating
In child1 count is 9
child1 is terminating

Java Thread Synchronization

In multithreading, there is the asynchronous behavior of the programs. If one thread is writing some data and another thread which is reading data at the same time, might create inconsistency in the application.

Java has provided synchronized methods to implement synchronized behavior.

In this approach, once the thread reaches inside the synchronized block, then no other thread can call that method on the same object. All threads have to wait till that thread finishes the synchronized block and comes out of that.

It can be written in the following form:

synchronized(object)
{
//Block of statements to be synchronized
}

Synchronization Example

public class Main {

public static void main(String args[]) {
PrintDemo printDemo = new PrintDemo();

ThreadDemo threadDemo1 = new ThreadDemo("Thread 1 ", printDemo);
ThreadDemo threadDemo2 = new ThreadDemo("Thread 2 ", printDemo);

threadDemo1.start();
threadDemo2.start();

// wait for threads to end
try {
threadDemo1.join();
threadDemo2.join();
} catch (Exception e) {
System.out.println("Threads Interrupted");
}
}

}
class PrintDemo {
public void printCount() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("Counter --- " + i);
}
} catch (Exception e) {
System.out.println("Thread interrupted.");
}
}
}
class ThreadDemo extends Thread {

private Thread t;
private final String threadName;
private final PrintDemo printDemo;

ThreadDemo(String name, PrintDemo printDemo) {
threadName = name;
this.printDemo = printDemo;
}

public void run() {
synchronized (printDemo) {
printDemo.printCount();
}
printDemo.printCount();
System.out.println("Thread " + threadName + " exiting.");
}

public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}

Example Results

With Synchronization
Starting Thread 1
Starting Thread 2
Counter --- 0
Counter --- 1
Counter --- 2
Counter --- 3
Counter --- 4
Thread Thread 1 exiting.
Counter --- 0
Counter --- 1
Counter --- 2
Counter --- 3
Counter --- 4
Thread Thread 2 exiting.
Without Synchronization
Starting Thread 1
Starting Thread 2
Counter --- 0
Counter --- 1
Counter --- 2
Counter --- 0
Counter --- 3
Counter --- 4
Counter --- 1
Counter --- 2
Counter --- 3
Thread Thread 1 exiting.
Counter --- 4
Thread Thread 2 exiting.

Inter-Thread Communication in Java

Inter-Thread Communication is the process in which two threads communicate with each other by using wait (), notify (), and notifyAll () methods.

The Thread which is required updation has to call the wait() method on the required object then immediately the Thread will be entered into a waiting state. So, The Thread which is performing updation of an object is responsible to give notification by calling notify () method.

After getting notification the waiting thread will get those updation.

There are three methods which are used for it.

wait()

Causes the current thread to wait until another thread invokes the notify().

notify()

Wakes up a single thread that is waiting on this object’s monitor.

notifyAll()

Wakes up all the threads that called wait( ) on the same object.

class Chat {
boolean flag = false;

public synchronized void Question(String msg) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(msg);
flag = true;
notify();
}

public synchronized void Answer(String msg) {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println(msg);
flag = false;
notify();
}
}

class T1 implements Runnable {
Chat m;
String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" };

public T1(Chat m1) {
this.m = m1;
new Thread(this, "Question").start();
}

public void run() {
for (int i = 0; i < s1.length; i++) {
m.Question(s1[i]);
}
}
}

class T2 implements Runnable {
Chat m;
String[] s2 = { "Hi", "I am good, what about you?", "Great!" };

public T2(Chat m2) {
this.m = m2;
new Thread(this, "Answer").start();
}

public void run() {
for (int i = 0; i < s2.length; i++) {
m.Answer(s2[i]);
}
}
}
public class TestThread {
public static void main(String[] args) {
Chat m = new Chat();
new T1(m);
new T2(m);
}
}

Output

Hi
Hi
How are you ?
I am good, what about you?
I am also doing fine!
Great!

Sources

https://www.tutorialspoint.com/java/java_multithreading.htmhttps://codezup.com/simple-java-program-inter-thread-communication/https://www.geeksforgeeks.org/multithreading-in-java/https://www.javatpoint.com/java-thread-setpriority-methodhttps://medium.com/s%C4%B1f%C4%B1rdan-i%CC%87leri-d%C3%BCzeye-java-e%C4%9Fitim-serisi/multithreaded-programlama-1-k%C4%B1s%C4%B1m-40904a219a18

--

--