Process and threads in Java
Java button on computer keyboard

A Process is an instance of program execution in progress. 

Let’s say: 

When you open a web browser that is a process.
When you open a media player that’s a process. 

Every single process has a unique identifier which can also be known as PID (Process Identifier). A Thread is the lowest unit of execution of a process. Several threads combine to make a process run successfully. 

So we can also consider thread as a lightweight process.
Each process has its memory allocation but inside a process, all the threads share a common memory. 

MULTITHREADING 

  • Whenever a programmer writes a program, the default execution of the statements written in that program is line by line. It means that a statement cannot get executed unless the statements before it are completely executed and their result is collected. This is called Sequential Execution or Line By Line execution. 

The application which executes a program in such a manner is called a Single-Threaded application. 

Example : 

class NoodleRecipe { 
public static void main(String[] args){ 
boilWater(); 
putNoodles(); 
putSaltAndPepper(); 
applyGarnish(); 
serve(); 
} 
public static void boilWater(){ 
System.out.println(“boiling water”);
} 
public static void putNoodles(){ 
System.out.println(“putting noodles in boiled water”); 
} 
public static void putSaltAndPepper(){ 
System.out.println(“putting salt and pepper”); 
} 
public static void applyGarnish(){ 
System.out.println(“applying garnish”); 
} 
public static void serve(){ 
System.out.println(“serve noodles”); 
} 
} 

In the above example, all the methods will be executed serially. 

REQUIREMENT OF MULTITHREADING PROCESSING: 

  • Now consider the scenario where there are two cashiers in the bank. Now two queues of customers can be made and requests of two customers can be taken parallelly. 
  • In this analogy, two threads are executing the same task of deposit/withdrawal. 

So the most basic requirement due to which multithreading arrived was to distinguish execution of tasks that do not have a dependency. These tasks can work upon some common data structure or data store. 

So we can define multithreading in one statement as follows : 

  • Multithreading is the ability of the CPU to execute multiple processes or threads simultaneously.

ADVANTAGES 

  • We can do several operations at the same time. 

Scenario:

  • The first thread may handle downloading images from the web. 
  • The second thread can handle input-output-related operations. 
  • The third thread can run for example simulations or numerical methods solving differential equations 

DISADVANTAGES 

  • Take care of the synchronization. 
  • It’s very hard to detect bugs. 
  • There are multiple operations so switching between threads is extremely expensive. 
  • The CPU has to save local data. 

Also Read: Java Interview Questions in 2021

LIFECYCLE / STATES OF THREAD EXECUTION: 

1. NEW – When we initiate a thread.

2. RUNNABLE – After we have started the thread and till the time the thread is processing. 

3. WAITING / NOT RUNNABLE – When a thread is waiting for some other operation. 

4. DEAD – After the thread finishes its operation processing. 

IMPLEMENTATION 

There are two ways to implement a thread.

  • By Extending the Thread class. 
  • By implementing the Runnable interface. 

IMPLEMENTATION BY EXTENDING THE THREAD CLASS: 

  • One way to create a new Thread is by providing a child of the Thread class. 
  • This Thread class is located in java.lang package. 
  • We extend this class and thus we can provide the implementation of the thread that we are creating. 
  • After extending the Thread class we need to override the run() method. By this approach we can provide the thread behavior/implementation in the run() method. 
  • After initializing the custom thread class object, we need to start the execution of the thread. Thread execution is started by calling the start() method on the custom thread object.

     
import java.lang.*; 
class ThreadCreationByExtendingDemo { 
public static void main( String args[] ) throws Exception { 
MyThread thread = new MyThread(); 
thread.start(); 
} 
} 
class MyThread extends Thread { 
@Override 
public void run() { 
System.out.println("Demo by extending Thread class"); 
} 
} 

IMPLEMENTATION BY IMPLEMENTING THE RUNNABLE INTERFACE:

  • The thread can be created by implementing the runnable interface. The runnable interface is located in java.lang package. 
  • After implementing the runnable interface we need to override the run() method in the interface. The run() method consists of the execution code for the thread.

After we provide the implementation of the run() method in our class, we use the following Thread class constructor in which we can pass the instance of our custom class. 

Thread(Runnable threadObj, String threadName); 

Thread(Runnable threadObj); 

After we have initialized the above-mentioned Thread constructor we need to start the thread. This can be done by calling the start() method upon the custom class object. 

Here is an example using this approach: 

import java.lang.*; 
class ThreadCreationByImplementingDemo implements Runnable { public void run() { 
try { 
for(int i = 0; i < 100; i++) { 
System.out.println(i); 
} 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} 
} 
} 
public class TestThread { 
public static void main(String args[]) { 
ThreadCreationByImplementingDemo obj = new 
ThreadCreationByImplementingDemo(); 
Thread t = new Thread(obj); 
t.start(); 
} 
} 

DAEMON THREAD 

  • A daemon thread runs in the background until the mail thread exists. As soon as the main thread completes all the daemon threads are killed by the JVM. A thread can be marked daemon as follows: Inner thread.setDaemon(true); 

SLEEPING THREAD 

  • A thread can be made dormant for a specified period using the following sleep method. One thing to remember is that the Thread.sleep() method throws an InterruptedException 

Thread.sleep(1000) //time in milliseconds 

Lets see a program on how to sleep a thread 

class SleepThreadExample { 
public static void main( String args[] ) throws Exception { 
MyThread thread = new MyThread(); 
Thread innerThread = new Thread(thread); 
innerThread.start(); 
System.out.println("Main thread exiting."); 
} 
static class MyThread implements Runnable { 
public void run() { 
System.out.println("Hello. innerThread going to sleep"); 
try { 
Thread.sleep(1000); 
} catch (InterruptedException ie) { 
} 
} 
} 
} 

Output : 

Main thread exiting. Hello. Inner thread going to sleep

IMPORTANT METHODS FOR THREAD IMPLEMENTATION

public void start() This method starts the thread. The thread is started and the code inside the run() method is executed.
public void run() Whenever a thread is started, the execution of this method begins.
public final void setName(String name) This changes the name of the thread with the name supplied.
public final void setPriority(int priority) This method sets the priority of the thread. The priority lies between 1 to 10.
public final void setDaemon(boolean on) When true is supplied to this method, this sets the thread as a daemon.
public final void join(long millisec) This blocks the current thread to pause execution till the second thread completes execution or the time supplied passes.
public void interrupt() This method interrupts the thread if it was blocked for some reason.
public final boolean isAlive() This method tells if the current thread is running or not.

THREAD PRIORITIES 

  • Each thread has a priority. Priorities are configured by using a number between 1 and 10. In most cases, the thread scheduler schedules the threads according to their priority which was configured (known as preemptive scheduling). It is not guaranteed because it depends on JVM specification which scheduling algorithm is chosen by the OS. 

Thread priorities are defined in the Thread class. 

  • public static int MIN_PRIORITY 
  • public static int NORM_PRIORITY 
  • public static int MAX_PRIORITY 

The default priority of a thread is 5, min priority is 1 and the max priority is 10.

class ThreadPriorityDemo extends Thread{ 
public void run(){ 
System.out.println("Thread Name :"+Thread.currentThread().getName()); System.out.println("Thread Priority :"+Thread.currentThread().getPriority()); 
} 
public static void main(String args[]){ 
ThreadPriorityDemo t1 =new ThreadPriorityDemo(); 
ThreadPriorityDemo t2 =new ThreadPriorityDemo();
t1.setPriority(Thread.MIN_PRIORITY); 
t2.setPriority(Thread.MAX_PRIORITY); 
t1.start(); 
t2.start(); 
} 
} 

Output 

Thread Name: Thread-0 

Thread Priority:10 

Thread Name: Thread-1 Thread Priority :1

THREAD GROUP 

  • Java provides features to group multiple threads in a single group. In this way, we can suspend, resume or interrupt a group of threads by a single method execution. 
  • Java thread group is implemented by java. lang.ThreadGroup class. 
  • A ThreadGroup represents a set of threads. A thread group can consist of other thread groups. 
  • A thread has access to the information about its thread group and its threads, but it cannot access the information about its thread group’s parent thread group or any other thread groups. 

Constructors of ThreadGroup Class 

ThreadGroup(String name) Creates a thread group with the specified name.
ThreadGroup(ThreadGroup parent, creates a thread group with the specified String name) parent group and name.

Methods of ThreadGroup class

void checkAccess() This method is used to check if the current running thread has access to modify the thread group.
int activeCount() Returns the approx number of active threads in the thread group.
void destroy() Destroys current thread group and all of its subgroups.
int getMaxPriority( )This method returns the thread which has the maximum priority in the thread group.
String ThreadG roup void getName() getParent() interrupt() This method returns the name of the specified thread group. This method returns the parent thread group of the thread group. This method interrupts all threads in the specified thread group.
boolean void isDaemon() setDaemon(boolean daemon)This method tests if the specified thread group is a daemon thread group. This method changes the daemon status of the specified thread group.
boolean isDestroyed() tests if the specified thread group has been destroyed.
void list() Provides information for the specified thread group to console output.
boolean parentOfThis method checks if the current thread group is the parent of the passed thread group.
void suspend() This is used to suspend all threads in the specified thread group.
void resume() This method is used to resume all threads in the thread group which was suspended using the previously mentioned suspend() method.
void setMaxPriorityThis method sets the maximum priority of the specified thread group.
void stop() This method is used to stop all threads in the specified thread group.

Example

public class ThreadGroupDemo implements Runnable{ 
public void run() { 
System.out.println(Thread.currentThread().getName()); 
} 
public static void main(String[] args) { 
ThreadGroupDemo runnable = new ThreadGroupDemo(); 
ThreadGroup tg1 = new ThreadGroup("Parent ThreadGroup"); 
Thread t1 = new Thread(tg1, runnable,"one"); 
t1.start(); 
Thread t2 = new Thread(tg1, runnable,"two"); 
t2.start(); 
Thread t3 = new Thread(tg1, runnable,"three"); 
t3.start(); 
System.out.println("Thread Group Name: "+tg1.getName()); 
tg1.list(); 
} 
} 

Output 

one 

two 

three 

Thread Group Name: Parent ThreadGroup 

java.lang.ThreadGroup[name=Parent ThreadGroup,maxpri=10] Thread[one,5,Parent ThreadGroup]

Thread[two,5,Parent ThreadGroup] 

Thread[three,5,Parent ThreadGroup] 

Also Read: Strings in Java

THREAD JOINS (WAITING FOR THREADS TO FINISH) 

  • The join() method waits for a thread to complete execution and die. In other words, when this method is used the current thread stops executing and waits till the thread it has been joined with completes and die. 

Methods 

public void join()throws InterruptedException 

public void join(long milliseconds)throws InterruptedException 

Example

class JoinMethodDemo extends Thread{ 
public void run(){ 
for(int i=1;i<=5;i++){ 
try{ 
Thread.sleep(500); 
}catch(Exception e){System.out.println(e);} 
System.out.println(i); 
} 
} 
public static void main(String args[]){ 
JoinMethodDemo t1=new JoinMethodDemo(); 
JoinMethodDemo t2=new JoinMethodDemo(); 
JoinMethodDemo t3=new JoinMethodDemo(); 
t1.start(); 
try{ 
t1.join(); 
}catch(Exception e){System.out.println(e);} 
t2.start(); 
t3.start(); 
} 
} 

Output : 123451122334455

SYNCHRONIZATION 

  • Whenever we want to control a resource while working with multiple threads, we use the concept of synchronization. 

The synchronization is mainly used to 

  • To prevent thread interference. 
  • To prevent consistency problems. 

There are two types of thread synchronization which are mentioned below 

  • Mutual Exclusion – This is achieved through the following ways

                  1. Synchronized method. 

2. Synchronized block. 

3. static synchronization. 

  • Cooperation (Inter-thread communication in java) 

Mutual Exclusion 

Mutual Exclusion helps keep threads from interfering with each other’s execution while sharing data. There are the following ways to achieve this.

1. By the synchronized method 

2. By synchronized block 

3. By static synchronization 

Synchronization is built around a lock or monitor. Every object has a lock associated with it. A thread that needs consistent access to an object’s fields has to acquire the object’s lock before starting to access them, and then release the lock when it’s done the processing. From Java 5 the package java.util.concurrent.locks contain multiple lock implementations. 

Multithreaded implementation without synchronization

class Table { 
void printTable(int n) {// method not synchronized 
for (int i = 1; i <= 5; i++) { 
System.out.println(n * i); 
try { 
Thread.sleep(400); 
} catch (Exception e) { 
System.out.println(e); 
} 
} 
} 
} 
class MyThread1 extends Thread { 
Table t; 
MyThread1(Table t) { 
this.t = t; 
} 
public void run() { 
t.printTable(5); 
} 
}
class MyThread2 extends Thread { 
Table t; 
MyThread2(Table t) { 
this.t = t; 
} 
public void run() { 
t.printTable(100); 
} 
} 
class TestSynchronization1 { 
public static void main(String args[]) { 
Table obj = new Table();// only one object 
MyThread1 t1 = new MyThread1(obj); 
MyThread2 t2 = new MyThread2(obj); 
t1.start(); 
t2.start(); 
} 
} 

Output : 5 100 10 200 15 300 20 400 25 500

Java synchronized method 

  • If we declare any method as synchronized by adding a synchronized keyword in its definition, it is known as a synchronized method. The synchronized method is used to lock an object for any shared resource so that multiple threads cannot access it at the same time. When a thread invokes a synchronized method, it automatically acquires the lock for that object and releases it when the thread execution of the thread completes. 
class Table { 
synchronized void printTable(int n) {// synchronized method 
for (int i = 1; i <= 5; i++) { 
System.out.println(n * i); 
try { 
Thread.sleep(400); 
} catch (Exception e) { 
System.out.println(e); 
} 
} 
} 
} 
class MyThread1 extends Thread { 
Table t; 
MyThread1(Table t) { 
this.t = t; 
} 
public void run() { 
t.printTable(5); 
} 
} 
class MyThread2 extends Thread { 
Table t;
MyThread2(Table t) { 
this.t = t; 
} 
public void run() { 
t.printTable(100); 
} 
} 
public class TestSynchronization2 { 
public static void main(String args[]) { 
Table obj = new Table();// only one object 
MyThread1 t1 = new MyThread1(obj); 
MyThread2 t2 = new MyThread2(obj); 
t1.start(); 
t2.start(); 
} 
} 

Output : 5 10 15 20 25 100 200 300 400 500

Synchronized Blocks 

  • Synchronized blocks can be used to implement synchronization on any specific part of a method. Suppose we have 50 lines of code in our method, but we want to synchronize only 5 lines, we can use a synchronized block. If we put all the codes of the method in the synchronized block, it will work the same as the synchronized method.
class Table { 
void printTable(int n) { 
synchronized (this) {// synchronized block 
for (int i = 1; i <= 5; i++) { 
System.out.println(n * i); 
try { 
Thread.sleep(400); 
} catch (Exception e) { 
System.out.println(e); 
} 
} 
} 
}// end of the method 
} 
class MyThread1 extends Thread { 
Table t; 
MyThread1(Table t) { 
this.t = t; 
} 
public void run() { 
t.printTable(5); 
} 
} 
class MyThread2 extends Thread { 
Table t; 
MyThread2(Table t) { 
this.t = t; 
}
public void run() { 
t.printTable(100); 
} 
} 
public class TestSynchronizedBlock1 { 
public static void main(String args[]) { 
Table obj = new Table();// only one object 
MyThread1 t1 = new MyThread1(obj); 
MyThread2 t2 = new MyThread2(obj); 
t1.start(); 
t2.start(); 
} 
} 

Output: 5 10 15 20 25 100 200 300 400 500

Inter-Thread Communication 

  • Inter-thread communication between threads is all about enabling synchronization among the threads. Inter-thread communication is a mechanism in which two threads are allowed to execute in the same critical section by using some constraints. It is implemented by using the following methods. These methods are implemented in the Object class: 

● wait()

● notify() 

● notifyAll() 

Wait Method 

  • This method is used when we want to make the current thread release the lock and wait until either another thread invokes the notify() method or the notifyAll() method on this object, or a configured amount of time has elapsed.
  • The current thread must own this object’s monitor, so it must be called from the synchronized method only, otherwise, it will throw an exception. 

Following is the syntax of the wait method.

public final void wait() throws InterruptedException waits until the object is notified.
public final void wait(long timeout)throws waits for the specified amount InterruptedException of time.

Notify Method 

  • This method wakes up a single thread that is waiting on this object’s monitor which was taken previously. If multiple threads are waiting on this object, any one of them is chosen to be awakened. The choice is random. 

Syntax 

public final void notify()

NotifyAll Method 

  • Wakes up every thread that is waiting on this object’s monitor. Syntax: Syntax
public final void notifyAll()

Example 

import java.util.LinkedList; 
import java.util.Queue; 
import java.util.Random; 
public class ProducerConsumerInJava { 
public static void main(String args[]) { 
System.out.println("Wait Notify Demo"); 
System.out.println("Solving Producer Consumer Problem"); 
Queue<Integer> buffer = new LinkedList<>(); 
int maxSize = 10; 
Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
producer.start(); 
consumer.start(); 
} 
} 
class Producer extends Thread { 
private Queue<Integer> queue; 
private int maxSize; 
public Producer(Queue<Integer> queue, int maxSize, String name){ super(name);
this.queue = queue; 
this.maxSize = maxSize; 
} 
@Override 
public void run() { 
while (true) { 
synchronized (queue) { 
while (queue.size() == maxSize) { 
try { 
System.out .println("Queue is full, " 
+ "Waiting till consumer thread takes some values from queue"); queue.wait(); 
} catch (Exception ex) { 
ex.printStackTrace(); 
} 
} 
Random random = new Random(); 
int i = random.nextInt(); 
System.out.println("Producing value : " + i); 
queue.add(i); 
queue.notifyAll(); 
} 
} 
} 
} 
class Consumer extends Thread { 
private Queue<Integer> queue; 
private int maxSize; 
public Consumer(Queue<Integer> queue, int maxSize, String name){ super(name); 
this.queue = queue; 
this.maxSize = maxSize;
} 
@Override 
public void run() { 
while (true) { 
synchronized (queue) { 
while (queue.isEmpty()) { 
System.out.println("Queue is empty," 
+ "Waiting till producer threads puts some data in the queue"); try { 
queue.wait(); 
} catch (Exception ex) { 
ex.printStackTrace(); 
} 
} 
System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); 
} 
} 
} 
}

Output 

The queue is empty, Waiting till producer threads put some data in the queue Producing value: -1692411980 

Producing value: 285310787 

Producing value: -1045894970 

Producing value: 2140997307 

Producing value: 1379699468 

Producing value: 912077154 

Producing value: -1635438928 

Producing value: -500696499 

Producing value: -1985700664 

Producing value: 961945684

The queue is full, Waiting till consumer thread takes some values from queue Consuming value: -1692411980 

Consuming value : 285310787 

Consuming value : -1045894970 

Consuming value : 2140997307 

Consuming value : 1379699468 

Consuming value : 912077154 

Consuming value : -1635438928 

Consuming value : -500696499 

Consuming value : -1985700664 

Consuming value : 961945684 

The queue is empty, Waiting till producer threads put some data in the queue Producing value: 1182138498 

Important Points Regarding Wait, Notify, NotifyAll 

  • We can use wait() and notify() methods to implement inter-thread communication in Java. 
  • Wait(), notify() and notifyAll() methods should be called from synchronized method or synchronized block or JVM.
  • Wait and notify methods should be called from a loop and never from if() block.

THREAD WAIT VS THREAD SLEEP

THREAD WAIT METHOD THREAD SLEEP METHOD
wait() method releases the lock sleep() method doesn’t release the lock.
is the method of Object class is the method of Thread class
is the non-static method is the static method
is the non-static method is the static method
should be notified by notify() or The sleep method completes execution notifyAll() methods when the specified time is elapsed.
0

LEAVE A REPLY

Please enter your comment!
Please enter your name here

19 − 9 =