When we execute java application we need good processors for quicker execution of the application. However good processor alone can’t help to run application fast. To create performance efficient application, multithreading is required.
What is multithreading?
Multithreading is a concept in which the application can create a small unit of tasks to execute in parallel. If you are working on a computer, it runs multiple applications and allocates processing power to them. A simple program runs in sequence and the code statements execute one by one. This is a single-threaded application. But, if the programming language supports creating multiple threads and passes them to the operating system to run in parallel, it is called multithreading.
When we talk about multithreading, we don’t care if the machine has a 2-core processor or a 16-core processor. Our goal is to create a multithreaded application and let the OS handle the allocation and execution part. In short, multithreading has nothing to do with multiprocessing.
Java is multithread programming language which means we can develop multithreaded program using java. A multithreaded program contains two or more parts that can run simultaneously, and each part can handle different tasks at the same time making efficient use of available resources.
By definition, multitasking is when multiple processes share common processing resources such as CPU. Multithreading extends the idea of multitasking into applications where one can subdivide specific operations within single application into individual threads. Each thread can run in parallel. The OS divides processing time not only among different applications, but also among each thread within an application.
Thread creation in Java
Java supports multithreading through Thread class and implementing runnable interface.
Java thread allows user to create a lightweight process that executes some tasks. We can create multiple threads in our program and start them. Java runtime will take care of creating machine-level instructions and work with OS to execute them in parallel.
Using Thread class
public class Main extends Thread {
public void run(){
System.out.println(“Thread by extending thread class”);
}
}
Using Runnable class
public class Main implements Runnable {
public void run(){
System.out.println(“Thread by implementing runnable interface”);
}
}
Thread creation by extending thread class or implementing runnable class depends on purpose of your class.
If your class want to extend other class, then it is recommended to implement runnable interface.
The reason is, when thread is created by extending Thread class, user cannot extend any other class in Java.
Thread Life cycle
Thread goes through various stages in its life cycle. For example, thread is created, started, runs and then dies. Following diagram shows complete life cycle of a thread
New state:
A new thread begins its life cycle in the new state. It remains in this state until the program starts the thread
Runnable state:
After a newly created thread is started, the thread becomes runnable. A Java thread in the RUNNABLE state could either be actually running on a CPU, or it could be waiting for a CPU to run on, but it is not waiting for anything else.
Waiting state:
A 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 state:
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) state:
A runnable thread enters the terminated state when it completes its task or otherwise terminates.
Blocked state:
A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait
Main thread in Java
When a Java program starts up, one thread begins running immediately. This is usually called the main thread of our program because it is the one that is executed when our program begins
There are certain properties associated with the main thread which are as follows:
Main thread is the thread from which other “child” threads will be spawned.
It must be the last thread to finish execution because it performs various shutdown actions
To control main thread, thread reference can be obtained using method currentThread(). This method returns a reference to main thread. Default priority of main thread is 5 and for all remaining user thread priority will be inherited from parent to child threads.
For each program, a Main thread is created by JVM(Java Virtual Machine). The “Main” thread first verifies the existence of the main() method, and then it initializes the class.
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 (1) and MAX_PRIORITY (10). By default, every thread is given priority 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 very much platform dependent
Thread synchronization
Synchronization in Java is the ability to control the access of multiple threads to any shared resource. Java thread synchronization is better option where we want to allow only one thread to access the shared resource.
Synchronization is mainly used to prevent thread interference and to prevent consistency problems. There are two types of synchronization Process synchronization and Thread synchronization. We will see Thread synchronization in more details
There are two types of thread synchronization mutual exclusive and inter-thread communication.
Mutual Exclusive
Cooperation (Inter-thread communication in java)
Mutual Exclusive
Mutual Exclusive thread synchronization type helps keep threads from interfering with one another while sharing data. It can be achieved by using the following three ways:
By Using Synchronized Method
By Using Synchronized Block
By Using Static Synchronization
Synchronization is achieved using internal entity known as lock or monitor. Every object has a lock associated with it. A thread that needs constant access to an object's data or resource has to acquire the object's lock before accessing them, and then release the lock when it's done with them
Inconsistent behavior - Without thread synchronization
Here in this example, we are printing table of 5 and 100 using two threads. In the output, you can see table of 5 and 100 are printed in random order due to no synchronization between threads
Example:
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
Consistent behavior - Using synchronization method option
Synchronized method is used to lock an object for any shared resource
When a thread invokes a synchronized method, it automatically acquires the lock for that object and releases it when the thread completes its task
Example:
// Example of java synchronized method
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
Consistent behavior - Using synchronization block option
Synchronized block can be used to perform synchronization on any specific resource of the method.
Example:
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);}
}
}
}
}
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
Consistent behavior - Using static synchronization option
If you make any static method as synchronized, the lock will be on the class not on object.
Example:
class Table
{
synchronized static void printTable(int n){ //static synchronization
for(int i=1;i<=5;i++){
System.out.println(n*i);
try{
Thread.sleep(400);
}catch(Exception e){}
}
}
}
class MyThread1 extends Thread{
public void run(){
Table.printTable(1);
}
}
class MyThread2 extends Thread{
public void run(){
Table.printTable(10);
}
}
class MyThread3 extends Thread{
public void run(){
Table.printTable(100);
}
}
public class TestSynchronization4{
public static void main(String t[]){
MyThread1 t1=new MyThread1();
MyThread2 t2=new MyThread2();
MyThread3 t3=new MyThread3();
t1.start();
t2.start();
t3.start();
}
}
Output:
1
2
3
4
5
10
20
30
40
50
100
200
300
400
500
Conclusion
Using threads in Java will enable flexibility to programmers in their programs. The simplicity of creating, configuring and running threads lets Java programmers devise portable and powerful applications that cannot be made in other third-generation languages. Threads allow any program to perform multiple tasks at once.