.
Contents
Java Multithreading 1
1. Introduction 2
2. Advantages 2
3. Thread Lifecycle 2
4. Thread Priority 3
5. Implementation of Multithreading 3
5.1 Implementation using Thread Class 3
5.2 Implementation using Runnable Interface 5
5.3 Thread Class vs Runnable Interface 6
6. Thread Group 6
7. Synchronization 7
7.1 Synchronized Method 8
7.2 Synchonized Block 8
7.3 Static Synchronization 9
8. Inter-Thread Communication 11
9. ExecutorService 12
.
.
.
.
.
.
.
.
.
.
.
.
.
.
• A thread is the smallest and lightweight process in an application.
• Multithreading is the process of executing multiple threads concurrently in an application.
• In simple language, multithreading helps to run applications faster by executing multiple processes at the same time.
.
.
• Multiple threads use shared memory, which saves space and allows faster switching between threads
• Better utilization of CPU
• Threads are independent
• Improved user experience
.
.
Following are the stages of life cycle of a thread:
.
.
• NEW: The thread is created (implemented) but not yet started by the program.
• RUNNABLE: The instance of the thread is invoked using the start method and given to scheduler for execution.
• RUNNING: The thread is executing and performing its task.
• BLOCKED: The thread is waiting or blocked while another thread completes its execution.
• TERMINATED: The thread has completed its execution.
.
• Each thread has a priority that helps the scheduler in ordering of the execution of threads.
• Priority is represented by a number from 1 to 10; where 1 is the MIN_PRIORITY, 5 the NORM_PRIORITY (also default) and 10 the MAX_PRIORITY
• Thread priority does not guarantee the order of execution of threads as it is dependent on the scheduling method chosen by the JVM.
.
5.Implementation of Multithreading
5.1Implementation using Thread Class
.
1.Create a class that is to executed as a thread and extend the java.lang.Thread class
2.Override and provide functionality implementation inside the run() method
3.Call start() method on the object of the class to submit the thread for execution.
.
Some important methods in the Thread class:
• start(): start a new thread; it internally invokes the run() method.
• run(): execution of the thread starts from this method. If run() method is called directly, a new thread will not be created rather the call goes to the same stack.
• currentThread(): returns the reference to current thread.
• getName(): returns the name of the current thread
• sleep(long time): suspends the thread for given time duration (in milliseconds)
• yield(): pause the current thread and allow other threads to execute
• join(): current thread invokes this method on second thread, and is blocked until the second thread completes execution.
• isAlive(): checks if the current thread is alive or terminated.
.
.
Example:
.
In this example, we create two threads and each thread executes a loop containing a print statement five times:
.
.
public class ThreadExample extends Thread {
@Override
public void run() {
try {
//get the ID of current thread
long currentThreadId = Thread.currentThread().getId();
for(int i = 0 ; i<5 ; i++)
{
System.out.println(LocalDateTime.now() +
“Running Thread ” + currentThreadId);
//suspend the current thread for two seconds
Thread.sleep(2000);
}
System.out.println(“Exiting Thread ” + currentThreadId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
.
.
public class TestThreadExample {
public static void main(String[] args) {
try {
//create two threads
ThreadExample thread1 = new ThreadExample();
thread1.start();
ThreadExample thread2 = new ThreadExample();
thread2.start();
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
|
Output:
.
2021-05-25T10:22:22.491313700Running Thread 14
2021-05-25T10:22:22.491313700Running Thread 13
2021-05-25T10:22:24.590311600Running Thread 13
2021-05-25T10:22:24.590311600Running Thread 14
2021-05-25T10:22:26.592790300Running Thread 14
2021-05-25T10:22:26.592790300Running Thread 13
2021-05-25T10:22:28.598846700Running Thread 14
2021-05-25T10:22:28.598846700Running Thread 13
2021-05-25T10:22:30.606945900Running Thread 13
2021-05-25T10:22:30.606945900Running Thread 14
Exiting Thread 14
Exiting Thread 13
|
.
.
.
5.2Implementation using Runnable Interface
.
1.The class that is to executed as a thread implements the java.lang.Runnable interface and overrides run() method provided by the interface.
2.Instantiate an object of Thread class, where the constructor parameter is the object of class created in previous step
3.Call start() method on the object created in previous step
.
Example:
.
In this example, we create two threads and each thread executes a loop containing a print statement five times:
.
.
public class RunnableExample implements Runnable {
@Override
public void run() {
try {
//get the ID of current thread
long currentThreadId = Thread.currentThread().getId();
for(int i = 0 ; i<5 ; i++)
{
System.out.println(LocalDateTime.now() + ” Running Thread ”
+ currentThreadId);
//suspend the thread for two seconds
Thread.sleep(2000);
}
System.out.println(“Exiting Thread ” + currentThreadId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
.
.
public class TestRunnableExample {
public static void main(String[] args) {
//create two threads
Thread thread1 = new Thread(new RunnableExample());
Thread thread2 = new Thread(new RunnableExample());
thread1.start();
thread2.start();
}
}
.
|
Output:
.
2021-05-24T11:38:29.830698800 Running Thread 14
2021-05-24T11:38:29.830698800 Running Thread 13
2021-05-24T11:38:31.904702900 Running Thread 14
2021-05-24T11:38:31.904702900 Running Thread 13
2021-05-24T11:38:33.912460600 Running Thread 14
2021-05-24T11:38:33.912460600 Running Thread 13
2021-05-24T11:38:35.925101100 Running Thread 14
2021-05-24T11:38:35.927102700 Running Thread 13
2021-05-24T11:38:37.940861700 Running Thread 13
2021-05-24T11:38:37.940861700 Running Thread 14
Exiting Thread 14
Exiting Thread 13
|
.
.
1.3Thread Class vs Runnable Interface
.
• If we extend the Thread class, extending any other class is not possible because Java doesn’t support multiple inheritance.
• Thread class provides some utility methods that could be helpful in implementation.
.
.
• It is possible to group multiple threads in a single object using java.lang.ThreadGroup class.
• In this way, multiple threads can be managed (e.g. suspend, resume) by a single method call.
• Thread group is maintained as a tree in which every thread group except the topmost one has a parent.
.
Creating a Thread Group:
public ThreadGroup(String name)
public ThreadGroup(ThreadGroup parent, String name)
.
Some of the important methods:
• String getName(): returns the name of this thread group.
• ThreadGroup getParent(): returns the parent of this thread group.
• void checkAccess(): determines if the currently running thread has the permission to modify this thread group.
• int activeCount(): returns the number of active threads in this thread group and its subgroups.
• int enumerate(Thread[] list): copies into the given array all active threads in this thread group and its subgroups.
• int activeGroupCount(): returns the number of active groups in this thread group and its subgroups.
• void interrupt(): interrupts all the threads in this thread group.
• void destroy(): destroys this thread group and its subgroups.
.
• In multithreaded environment, multiple threads could try to access shared resources at the same time and it could produce undesirable results.
• Synchronization helps to ensure that only one thread accesses a shared resource at a given time.
• The keyword ‘synchronized’ is used to achieve synchronization at the block or method level.
• With synchronization, one thread acquires a lock on the object for executing particular code and then releases the lock for other threads to access it.
Let’s consider the following example to understand the need of synchronization.
The class ‘HelloExample’ contains a method to print the given name in particular format and multiple thread objects of class ‘ThreadExample’ access this method. It produces inconsistent output, so synchronization is required for the threads to print each name in expected format one at a time.
.
public class HelloExample {
public void print(String name) {
try {
System.out.println(“Hello..”);
Thread.sleep(2000);
System.out.println(name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
.
public class ThreadExample extends Thread {
HelloExample helloExample;
String name;
ThreadExample(HelloExample helloExample, String name)
{
this.helloExample = helloExample;
this.name = name;
}
@Override
public void run() {
helloExample.print(name);
}
}
.
public class TestThreadExample {
public static void main(String[] args) {
HelloExample helloExample = new HelloExample();
//create three threads
ThreadExample thread1 = new ThreadExample(helloExample, “Nilton”);
thread1.start();
ThreadExample thread2 = new ThreadExample(helloExample, “Ankit”);
thread2.start();
ThreadExample thread3 = new ThreadExample(helloExample, “Amit”);
thread3.start();
}
}
.
|
Expected Output:
Hello..
Nilton
Hello..
Amit
Hello..
Ankit
.
Actual Output:
Hello..
Hello..
Hello..
Nilton
Ankit
Amit
|
.
.
• The thread acquires lock on the whole method i.e. no other thread can access the method until the current thread has finished its execution.
.
public class HelloExample {
public synchronized void print(String name) {
try {
System.out.println(“Hello..”);
Thread.sleep(2000);
System.out.println(name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
|
Output:
Hello..
Nilton
Hello..
Amit
Hello..
Ankit
|
.
.
• The thread acquires lock on a part of the method i.e. specific lines of code inside the block ; so other threads can still access the method and execute the rest of the code.
public class ThreadExample extends Thread {
HelloExample helloExample;
String name;
ThreadExample(HelloExample helloExample, String name)
{
this.helloExample = helloExample;
this.name = name;
}
@Override
public void run() {
synchronized (helloExample) {
helloExample.print(name);
}
}
}
|
Output:
Hello..
Nilton
Hello..
Amit
Hello..
Ankit
|
.
.
7.3Static Synchronization
.
• If we use synchronized on a static method, then the lock is acquired on the class and not on the object. This means when the shared resource belongs to all instances of a class i.e. it is static, then we use static synchronization.
Example:
Now we modify the previous example and create two objects of ‘HelloExample’ to be shared by four threads. Since the lock is on the objects ‘helloExample1’ and ‘helloExample2’, threads ‘thread0’ and ‘thread1’ will not interfere, and threads ‘thread2’ and ‘thread3’ will not interfere.
public class HelloExample {
public synchronized void print(String name) {
try {
System.out.println(Thread.currentThread().getName() + ” Hello..”);
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ” ” + name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
.
public class ThreadExample extends Thread {
HelloExample helloExample;
String name;
ThreadExample(HelloExample helloExample, String name)
{
this.helloExample = helloExample;
this.name = name;
}
@Override
public void run() {
helloExample.print(name);
}
}
.
public class TestThreadExample {
public static void main(String[] args) {
HelloExample helloExample1 = new HelloExample();
HelloExample helloExample2 = new HelloExample();
ThreadExample thread0 = new ThreadExample(helloExample1, “Nilton”);
thread0.start();
ThreadExample thread1 = new ThreadExample(helloExample1, “Ankit”);
thread1.start();
ThreadExample thread2 = new ThreadExample(helloExample2, “Amit”);
thread2.start();
ThreadExample thread3 = new ThreadExample(helloExample2, “Jack”);
thread3.start();
}
}
.
|
Output:
Thread-3 Hello..
Thread-0 Hello..
Thread-0 Nilton
Thread-1 Hello..
Thread-3 Jack
Thread-2 Hello..
Thread-2 Amit
Thread-1 Ankit
|
.
We get inconsistent output, because ‘thread0’ or ‘thread1’ can still interfere with ‘thread2’ or ‘thread3’ because they acquire lock on different objects.
To fix this, we use static synchronization:
public class HelloExample {
public static synchronized void print(String name) {
try {
System.out.println(Thread.currentThread().getName() + ” Hello..”);
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ” ” + name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
|
Output:
Thread-0 Hello..
Thread-0 Nilton
Thread-3 Hello..
Thread-3 Jack
Thread-2 Hello..
Thread-2 Amit
Thread-1 Hello..
Thread-1 Ankit
|
.
8.Inter-Thread Communication
.
It is possible for synchronized threads to communicate with each other regarding the lock status of a shared resource. One thread can pause its execution in a synchronized section and allow another thread to execute, and then resume the first thread.
This can be achieved using the following methods:
• wait() : It causes the calling thread to release the lock and pause its execution until another thread calls notify() or notifyAll() on the same object.
• notify() : It wakes up a single thread that had called wait() on the same object.
• notifyAll() : It wakes up all the threads that had called wait() on the same object.
Example:
In the following example, we have a one thread sets the username variable and the other thread prints it. The printUserThread waits until the AddUserThreads set the value and calls notify, and then it prints it.
public class HelloExample {
private String username;
public synchronized void print() {
try {
if(username == null){
System.out.println(Thread.currentThread().getName() + ” No username
found..waiting..”);
wait();
System.out.println(Thread.currentThread().getName() + ” Hello..” +
username);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void add(String name) {
username = name;
System.out.println(Thread.currentThread().getName() + ” User added.
notifying..”);
notify();
}
}
.
public class AddUserThread extends Thread {
HelloExample helloExample;
String name;
AddUserThread(HelloExample helloExample, String name)
{
this.helloExample = helloExample;
this.name = name;
}
@Override
public void run() {
helloExample.add(name);
}
}
.
public class PrintUserThread extends Thread {
HelloExample helloExample;
PrintUserThread(HelloExample helloExample)
{
this.helloExample = helloExample;
}
@Override
public void run() {
helloExample.print();
}
}
.
public class TestThreadExample {
public static void main(String[] args) {
HelloExample helloExample = new HelloExample();
PrintUserThread printUserThread = new PrintUserThread(helloExample);
printUserThread.start();
AddUserThread addUserThread = new AddUserThread(helloExample,
“Nilton”);
addUserThread.start();
}
}
.
|
Output:
Thread-0 No username found..waiting..
Thread-1 User added. notifying..
Thread-0 Hello..Nilton
|
.
.
public interface ExecutorService extends Executor
.
• The ExecutorService interface, defined in java.util.concurrent package, helps in asynchronous execution of tasks on large number of threads.
• The Executor maintains a thread pool and is responsible for assigning the tasks to threads from the pool and executing them.
• The implementation of ExecutorService can execute tasks which implement Runnable or Callable interface.
• The Callable interface returns a ‘Future’ object that can be used to get the status and result of the task.
Creating an ExecutorService:
ExecutorService executorService = Executors.newFixedThreadPool(3); //creates a pool of 3 threads
ExecutorService executorService = Executors.newSingleThreadExecutor() //creates ExecutorService with 1 thread
.
ExecutorService executorService = Executors. newScheduledThreadPool(3);//creates a pool of 3 threads with scheduling option
.
Example:
Following is an example that shows the simplest implementation of ExecutorService:
public class ExecutorExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runnable runnableTask = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ”
(runnable) ” + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Callable<String> callableTask = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + ” (callable)
” + LocalDateTime.now());
return “callable executed!”;
}
};
//submit Runnable task
executorService.submit(runnableTask);
//submit callable task
Future<String> future = executorService.submit(callableTask);
System.out.println(“callable done? ” + future.isDone());
System.out.println(“callable result: ” + future.get());
executorService.shutdown();
}
}
.
|
Output:
callable done? false
pool-1-thread-2 (callable) 2021-05-30T22:09:27.375807900
callable result: callable executed!
pool-1-thread-1 (runnable) 2021-05-30T22:09:29.313810300
|
.
Leave a Reply