Java Multithreading Simplifed

Serial Threaded vs Multi-threaded -

Prateek
25 min readJan 20, 2023

Multi-threaded applications normally do parallel processing of tasks where as single threaded applications do one task at a time i.e. If there are two tasks such as T1, T2 they are executed in serial order i.e T1 after T2 or T2 after T1, where as multi threading enables us to execute them simultaneously i.e. T1 along with T2. We can choose single threaded applications when parallel processing is not required example use cases such as simple text editor.

Below is a simple example for single threaded application, in this the Task need to print 1500 T’s and Main is another task which prints 1500 M’s. If we execute this in serial order then it is referred as serial execution.

class Task {
public void doTask() {
for (int i = 1; i <= 1500; i++) {
System.out.print("T");
}
}
}

/* Main */
public class Main {
public static void main(String[] args) {
// Print M's
for (int i = 1; i <= 1500; i++) {
System.out.print("M");
}
// Call the task to print T's
Task t1 = new Task();
t1.doTask();
}
}

When you run the above example you will see 1500 M’s first and then followed by 1500 T’s.

True Parallelism vs Logical Parallelism

True parallelism is achieved by assigning tasks to individual CPUs or Processors. This is possible through multicore processors or executing the tasks using multiple CPUs.

If there is one CPU and you want to perform multiple tasks in parallel, then CPU will be shared across those tasks for some stipulated time interval, which stands for interleaved execution, and this way of executing the tasks is known as logical parallelism or psuedo parallelism.

In case of logical parallelism, lets assume there are two tasks T1 and T2, when executed in parallel and using one CPU, CPU is switched between T1 and T2 i.e. CPU executed T1 for some stipulated time interval and then switched to T2. Once T2’s time slice is completed then it is switched back to T1 and starts from where it stops.

Example — (Explained in detail in future lectures.)

In the below example Task is a Thread(explained later), and run is the entry point of the thread execution where it starts printing 1500 T’s.

main() runs in Thread i.e. the Main thread which is started by the JVM.

Note In the main method we are not calling doTask directly, instead we are using the start() method of the Thread class, which runs the Task using a separate Thread.

class Task extends Thread {

// Thread execution begins here.
public void run() {
// performs the task i.e. prints 1500 T's
doTask();
}

public void doTask() {
for(int i=1; i <= 1500; i++) {
System.out.print("T");
}
}
}

public class Main {

// Runs with in the Main thread started by JVM.
public static void main(String[] args) {

Task t1 = new Task();
// Starts a separate Thread using the start method of the Thread class.
t1.start();

// runs in the Main thread and prints 1500 M's
for(int i=1; i <= 1500; i++) {
System.out.print("M");
}
}
}

Here main() and Task are run using two separate threads, which means they are executed in parallel (logical parallelism in case of single CPU) and hence you will see output like MMMTTTMMMTTT…

— — —

Thread -

A thread is a light weight process, it is given its own context and stack etc. for preserving the state. Thread state enables the CPU to switch back and continue from where it stopped.

Creating Threads in Java -

When you launch Java application, JVM internally creates few threads, e.g. Main thread for getting started with main() and GarbageCollector for performing garbage collection and few other threads which are need for execution of a Java application.

You can create a thread and execute the tasks with in the application, this enables you to perform parallel activities with in the application.

There are two approaches,

1) Extending the Thread class and performing the task. This is not a preferred approach because you are not extending the Thread functionality, instead you are using the Thread to execute a task, hence you should prefer the second approach.

2) Implementing the Runnable interface and then submitting this task for execution. Similarly there is a Callable interface(explained later) as well.

run() method -

Once you choose your approach, you can consider the run() method as the entry point for thread execution. To simplify just think like main() for a program, run() for a thread.

start() method -

Execution of the thread should be initiated using the start() method of the Thread class. This submits the thread for execution. This takes the associated thread to ready state, this doesn’t mean it is started immediately. i.e. in simple terms, when you call the start() method, it marks the thread as ready for execution and waits for the CPU turn.

// 1. Extending the Thread class.
class MyThread extends Thread {

// Thread execution begins here.
public void run() {
for (int i = 0; i <= 1000; i++) {
System.out.print("T");
}
}
}

// 2. Implementing the Runnable interface, This marks this class as Runnable and
// assures that this class contains the run() method. Because any class implementing the
// interface should define the abstract method of the interface, otherwise it becomes abstract.
class MyTask implements Runnable {

// Thread execution begins here.
@Override
public void run() {
// DO THAT TASK HERE.
for (int i = 0; i <= 1000; i++) {
System.out.print("-");
}
}
}

public class Main {

// Will run with in the main thread.
public static void main(String[] args) {

// Because MyThread extends the Thread
// class, you can call the start() method
// directly, as it is also a member of this
// class, courtesy inheritance relation.
MyThread thr = new MyThread();
thr.start();

// MyTask is a Runnable task and not a
// Thread and hence we need to create a
// Thread object and assign it the task
// Note here we are calling the start
// method over Thread object and not on
// task object.
MyTask task = new MyTask();
Thread thr2 = new Thread(task);
thr2.start();

for (int i = 0; i <= 1000; i++) {
System.out.print("M");
}
}
}

There are three threads in the above program (system threads ignored). One the Main thread that prints “M” and thr that prints “T” and thr2 that prints “-”. Because they are executed in parallel, you will see the output like MMMTTT — -MMMTTT — — …

— — —

Thread -

A thread is a light weight process, it is given its own context and stack etc. for preserving the state. Thread state enables the CPU to switch back and continue from where it stopped.

Creating Threads in Java -

When you launch Java application, JVM internally creates few threads, e.g. Main thread for getting started with main() and GarbageCollector for performing garbage collection and few other threads which are need for execution of a Java application.

You can create a thread and execute the tasks with in the application, this enables you to perform parallel activities with in the application.

There are two approaches,

1) Extending the Thread class and performing the task. This is not a preferred approach because you are not extending the Thread functionality, instead you are using the Thread to execute a task, hence you should prefer the second approach.

2) Implementing the Runnable interface and then submitting this task for execution. Similarly there is a Callable interface(explained later) as well.

run() method -

Once you choose your approach, you can consider the run() method as the entry point for thread execution. To simplify just think like main() for a program, run() for a thread.

start() method -

Execution of the thread should be initiated using the start() method of the Thread class. This submits the thread for execution. This takes the associated thread to ready state, this doesn’t mean it is started immediately. i.e. in simple terms, when you call the start() method, it marks the thread as ready for execution and waits for the CPU turn.

class MyThread extends Thread {
public void run(){
System.out.println("T1 : "+ Thread.currentThread().getName());
for (int i = 0; i <= 1000; i++) {
System.out.print("T");
}
}
}

class MyTask implements Runnable {

@Override
public void run() {
System.out.println("T2 : "+ Thread.currentThread().getName());
for (int i = 0; i <= 1000; i++) {
System.out.print("-");
}
}
}

class Main{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();

MyTask myTask = new MyTask();
Thread thread = new Thread(myTask);
thread.start();

System.out.println("Main :"+ Thread.currentThread().getName());
for (int i = 0; i <= 1000; i++) {
System.out.print("M");
}

}
}

There are three threads in the above program (system threads ignored). One the Main thread that prints “M” and thr that prints “T” and thr2 that prints “-”. Because they are executed in parallel, you will see the output like MMMTTT — -MMMTTT — — …

— — — — — — — —

a.txt

=== Prateek

By Rajesh Saha, Anupam Mishra: Workers of the Indian Secular Front, or ISF, were lathicharged by the police in Kolkata’s Esplanade area this evening as they blocked roads in the area. ISF claimed they were attacked by the Trinamool Congress workers on their way to a rally in the area and demanded the arrest of Trinamool leader Arabul Islam for orchestrating an earlier attack on them this morning.

Kolkata police began the lathicharge after traffic came to a standstill due to the road blockade. ISF workers also allegedly threw stones at the police, who had to use tear gas shells to disperse the crowd and tackle the situation.

Seventeen people were arrested by the police, while ISF leader and MLA Noushad Siddiqui was detained.

The Indian Secular Front, floated by Pirzada Abbas Siddiqui just ahead of the 2021 Bengal assembly elections, was celebrating its foundation day today. Hundreds of party workers gathered in the Esplanade area to mark the day.

Earlier in the day, workers of the TMC and the ISF clashed in the state’s south 24 pargana’s Bhangar area when members of the ISF were on their way to Kolkata. ISF workers allegedly torched a Trinamool party office during the clashes.

b.txt

=== John

By Rajesh Saha, Anupam Mishra: Workers of the Indian Secular Front, or ISF, were lathicharged by the police in Kolkata’s Esplanade area this evening as they blocked roads in the area. ISF claimed they were attacked by the Trinamool Congress workers on their way to a rally in the area and demanded the arrest of Trinamool leader Arabul Islam for orchestrating an earlier attack on them this morning.

Kolkata police began the lathicharge after traffic came to a standstill due to the road blockade. ISF workers also allegedly threw stones at the police, who had to use tear gas shells to disperse the crowd and tackle the situation.

Seventeen people were arrested by the police, while ISF leader and MLA Noushad Siddiqui was detained.

The Indian Secular Front, floated by Pirzada Abbas Siddiqui just ahead of the 2021 Bengal assembly elections, was celebrating its foundation day today. Hundreds of party workers gathered in the Esplanade area to mark the day.

Earlier in the day, workers of the TMC and the ISF clashed in the state’s south 24 pargana’s Bhangar area when members of the ISF were on their way to Kolkata. ISF workers allegedly torched a Trinamool party office during the clashes.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class IOUtils {
public static void copy(InputStream src, OutputStream dest) throws IOException {
int value;
while ((value = src.read()) != -1) {
dest.write(value);
}
}

public static void copyFile(String src, String dest) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);

IOUtils.copy(fis, fos);

fis.close();
fos.close();
}
}
import java.io.IOException;

class CopyTask implements Runnable {
String sourceFile;
String destFile;

public CopyTask(String sourceFile, String destFile) {
this.sourceFile = sourceFile;
this.destFile = destFile;
}

@Override
public void run() {
try {
IOUtils.copyFile(sourceFile, destFile);
System.out.println("Thread :" + Thread.currentThread().getName()
+ ", Copied from - " + sourceFile + " to " + destFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}

class Main {
public static void main(String[] args) throws IOException {
String sourceFile1 = "a.txt";
String destFile1 = "c.txt";

String sourceFile2 = "b.txt";
String destFile2 = "d.txt";

Thread t1 = new Thread(new CopyTask(sourceFile1, destFile1));
t1.start();

Thread t2 = new Thread(new CopyTask(sourceFile2, destFile2));
t2.start();

System.out.println("Done!!");
}
}

Important Note -

Although the main thread is completed after starting the two other threads, application won’t be terminated until both the threads are done with the copy.

Let’s improve above example using ExecutorService.

class Main {
public static void main(String[] args) throws IOException {
String sourceFile1 = "a.txt";
String destFile1 = "c.txt";

String sourceFile2 = "b.txt";
String destFile2 = "d.txt";

ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new CopyTask(sourceFile1, destFile1));
executor.execute(new CopyTask(sourceFile2, destFile2));

System.out.println("Done!!");
}
}

Done!!
Thread :Thread-1Copied from — b.txt to d.txt
Thread :Thread-0Copied from — a.txt to c.txt

ExecutorService -

In the previous example we executed the copyTask using separate threads, but there is a critical point to consider here, Thread creation is a costly activity as it includes creating a separate execution context, stack etc.. Hence we should refrain from creating too many threads. And also creating a thread for each task is not a good idea, instead we can create a pool of threads and effectively utilise them in executing all the task. This could be achieved using ExecutorService in Java. Use the execute method of the ExecutorService to submit a Runnable task, if a thread is available in the pool then it assigns this task to the thread otherwise the task is added to the blocking queue and is kept till a thread is available.

Creating a ThreadPool -

Below statement creates a thread pool of size 5.

ExecutorService executor = Executors.newFixedThreadPool(5);

Submitting a Runnable task -

We can submit a task for execution using the execute method.

executor.execute( runnableTaskInstance );

e.g.

executor.execute(new CopyTask(sourceFile1, destFile1));

— — — —

Stopping threads in the middle -

stop() method of the Thread class could be used to stop the thread in the middle. But this is the dangerous thing to do as it leaves the system in inconsistent state, because we are not giving the opportunity to the thread to rollback or reverse the actions that it has taken. And hence the stop method is deprecated and should not be used.

Correct approach would be to call the interrupt() method on the thread and then it is up to the thread to consider whether to stop or not.

A thread can then check if it was interrupted or not using interrupted() method of the Thread class. We can design the thread in a way that it reverses the actions/operations performed and then stop when interrupted.

Note — If you are not extending Thread class and instead implementing Runnable interface, then you can use Thread.isInterrupted() to check if the current thread is interrupted.

interrupted() and Thread.isInterrupted() both methods return true if the thread is interrupted when it is alive.


class MyThread extends Thread {
public void run() {
for ( ;; ) {
if(interrupted()){
System.out.println("Thread is interrupted, hence stopping...");
break;
}
System.out.print("T");
}
}
}

class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start();

try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}

thread1.interrupt();
}
}

sleep() method -

sleep() method of the thread class is used to block the thread for the given time interval in milliseconds. This method throws InterruptedException if the thread is interrupted while it is in sleep.

— — — -

Thread States -

A thread can be in one of the following states:

NEW

A thread that is created but not yet started is in this state.

RUNNABLE

A thread executing in the Java virtual machine is in this state, internally we can think of it as a combination of two sub states Ready and Running, i.e. when you start the thread it comes to Ready state and wait for the CPU, and if CPU is allocated then it goes into Running state. Once allocated CPU time is completed, in other words when the Thread schedular suspends the thread then it goes back to the Ready state and waits for its turn again.

The yield() method instructs the thread schedular to pass the CPU to other waiting thread if any.

BLOCKED

A thread is blocked if it is waiting for a monitor lock is in this state. Refer synchronized methods and blocks.

WAITING

A thread that is waiting indefinitely for another thread to perform a particular action is in this state. Refer wait(), join()

TIMED_WAITING

A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state. Refer wait(millis), join(millis)

TERMINATED

A thread that has exited i.e. it has either completed its task or terminated in the middle of the execution.

yield() method -

yield() method is important in few scenarios, suppose a thread is given 5 min of CPU time, now after a minute thread knows that it doesn’t need the CPU anymore with in that time period, in such scenarios do you think that blocking the CPU for the next four minutes is a good idea ? No, it is better to pass on the control to the threads if any waiting for CPU and that is when we can use the yield() method. Usage Thread.yield(), it is a static method of the Thread class and it affects the current thread from which the method is invoked.

— — —

Notes — Thread Priorities

Let us look at how we can change the thread priorities.

Thread priorities range between 1 and 10.

MIN_PRIORITY — 1 being the minimum priority

NORM_PRIORITY — 5 is the normally priority, this is the default priority value.

MAX_PRIORITY — 10 being the max priority.

setPriority(int newPriority) -

A method in the Thread class, this is used to set the new priority for the thread. If the newPriority value is more than the maximum priority allowed for the group then maximum priority is considered, i.e. if you try to set 15 then it takes only 10. And for a given ThreadGroup if the maximum allowed priority is 7 then any thread with in that group can have a maximum of 7.

Example -

Just think about a software installer app, the thread that copies the files should be given more priority than the thread which display the progress etc, that speeds up the installation process. Below example demonstrates setting higher priority for copyThread.

— — — — — -

Thread.currentThread() -

currentThread() is a static method in the class Thread and all the static method in the Thread class normally operate on the thread in which it is being executed. Here Thread.currentThread() returns a reference to the current thread i.e. the main thread.

getThreadGroup() — [Thread class method]

A Thread class method that returns a reference to the ThreadGroup to which the corresponding thread instance belongs.

getParent() — [ThreadGroup class method]

A ThreadGroup class method that returns a reference to the parent thread group if any. If there is no parent then this method returns null.

setMaxPriority(int maxPriority) — [ThreadGroup class method]

Sets the maximum priority for that group so that no thread can exceed this priority with in the group.

class MyTask implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

class Main {
public static void main(String[] args) {
ThreadGroup myThreadGroup = new ThreadGroup("MyGroup");
myThreadGroup.setMaxPriority(4);

Thread myThread = new Thread(myThreadGroup, new MyTask(), "Demo Thread");
myThread.start();

System.out.println("System threads");
Thread thread = Thread.currentThread();
ThreadGroup threadGroup = thread.getThreadGroup();

while (threadGroup.getParent() != null) {
threadGroup = threadGroup.getParent();
}
threadGroup.list();
}
}
System threads
java.lang.ThreadGroup[name=system,maxpri=10]
Thread[Reference Handler,10,system]
Thread[Finalizer,8,system]
Thread[Signal Dispatcher,9,system]
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread[Demo Thread,5,main]
java.lang.ThreadGroup[name=MyGroup,maxpri=4]
java.lang.ThreadGroup[name=InnocuousThreadGroup,maxpri=10]
Thread[Common-Cleaner,8,InnocuousThreadGroup]

Technical Note -

It is important to note that even the main method runs with a thread called the main thread. And its default priority is 5.

— — — — -

Notes — Daemon Threads

Daemon threads are the ones which does not prevent the JVM from exiting the application once it finishes. Daemon threads are handy for performing background tasks such as garbage collection or collecting application statistics etc. Note that the Java Virtual Machine exits when the only threads running are all daemon threads.

Example -

In the below example at the end of the while loop grp will point to system thread group. And enumerate method lists the threads in that group and copies the references to the given array. It also returns the number of threads copied. And later I am printing the thread name along with the boolean value checking if it is a daemon thread or not, using the method isDaemon().

public class Main1 {

public static void main(String[] args) {

System.out.println("System threads..........");

Thread thr = Thread.currentThread();
ThreadGroup grp = thr.getThreadGroup();
while (grp.getParent() != null) {
grp = grp.getParent();
}

Thread[] threads = new Thread[10];
int n = grp.enumerate(threads);

for (int i = 0; i < n; i++) {
System.out.println(
"Thread Name: " + threads[i].getName() +
" ; isDaemon: " + threads[i].isDaemon());
}
}
}
  1. System threads……….
  2. Thread Name: Reference Handler ; isDaemon: true
  3. Thread Name: Finalizer ; isDaemon: true
  4. Thread Name: Signal Dispatcher ; isDaemon: true
  5. Thread Name: main ; isDaemon: false

You can see that Reference Handler, Finalizer, Single Dispatcher all these are daemon threads because they run the background and they doesn’t stop the application from exiting.

setDaemon(boolean on) -

The method of the Thread class makes a enables us to set whether a thread is a daemon thread or user thread.

class MyTask implements Runnable {

@Override
public void run() {
for (;;) {
System.out.print("T");
}
}
}
public class Main {
public static void main(String[] args) {
Thread thr = new Thread(new MyTask());
thr.setDaemon(true);
thr.start();

for (int i=1; i <= 200; i++) {
System.out.print(" M ");
}
}
}

Output -

MMTTMMT

A combination of M and T but the application ends once the main ends.

— — — — — — — —

Callable Task

Callable interface -

Unlike Runnable, Callable interface allows us to create an asynchronous task which is capable of returning an Object.

interface Callable<V> {
V call() throws Exception;
}

If you implement Runnable interface we can not return any result. But if you implement Callable interface then you can return the result as well. Like run() method in the Runnable interface, you need to override the call() method. The return type of the call() method should match with the intended return type of the result. Callable<Double> means the call method returns Double value, Callable<Fruit> means call method returns an instance of type Fruit.

class GetStockQuoteTask implements Callable<Double> {

private String stockSymbol;

public GetStockQuoteTask(String stockSymbol) {
this.stockSymbol = stockSymbol;
}

public Double call() {
// Write some logic to fetch the stock price
// for the given symbol and return it.
return 0.0;
}
}

To submit this task for execution, you can use the submit method on the ExecutorService.

String symbol = "ABCD";
GetStockQuoteTask task = new GetStockQuoteTask(symbol);
Future<Double> future = executor.submit( task );
Double price = future.get();

Future Object -

When you submit a Callable task to the ExecutorService, it returns a Future object. This object enables us to access the request and check for the result of the operation if it is completed.

Important methods -

isDone() — Returns true if the task is done and false otherwise.

get() — Returns the result if the task is done, otherwise waits till the task is done and then it returns the result.

cancel(boolean mayInterrupt) — Used to stop the task, stops it immediately if not started, otherwise interrupts the task only if mayInterrupt is true.

class MyMath {
public static int add(int a, int b) {
return a + b;
}
}

class Main {

public static void main(String[] args) throws Exception {
int x = 10;
int y = 20;
ExecutorService executor = Executors.newFixedThreadPool(1);

// Submit a Callable task and use the Future object to fetch the result.
Future<Integer> future = executor.submit(new Callable<Integer>() {
public Integer call() {
return MyMath.add(x, y);
}
});

// do some parallel task Inefficient to simply wait,
// instead you can release the CPU by calling Thread.yield() insid the while loop.
while (!future.isDone())
; // wait

// fetch the result
int z = future.get();

System.out.println("Result is " + z);
}
}

Pattern Search

public class Main {

public static void main(String[] args) throws Exception {

String pattern = "public";

File dir = new File("./src/sample");
File [] files = dir.listFiles();

PatternFinder finder = new PatternFinder();

// Fixed thread pool of size 3.
ExecutorService executor = Executors.newFixedThreadPool(3);

// Map to store the Future object against each
// file search request, later once the result is obtained
// the Future object will be
// replaced with the search result.

Map<String, Object> resultMap = new HashMap<String,Object>();

long startTime = System.currentTimeMillis();

for (File file : files) {

// Submit a Callable task for the file.
Future<List<Integer>> future =
executor.submit(
new Callable<List<Integer>>() {
public List<Integer> call() {
List<Integer> lineNumbers = finder.find(file, pattern);
return lineNumbers;
}
});

// Save the future object in the map for
// fetching the result.

resultMap.put(file.getName(), future);
}

// Wait for the requests to complete.
waitForAll( resultMap );

// Display the result.
for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
System.out.println(
pattern + " found at - " + entry.getValue() +
" in file " + entry.getKey());
}


System.out.println(
" Time taken for search - "
+ (System.currentTimeMillis() - startTime));

}

private static void waitForAll(Map<String, Object> resultMap)
throws Exception {

Set<String> keys = resultMap.keySet();

for (String key : keys) {
Future<List<Integer>> future =
(Future<List<Integer>>) resultMap.get(key);

while (! future.isDone()) {

// Passing the CPU to other
// threads so that they can
// complete the operation.
// With out this we are simply
// keeping the CPU in loop and
// wasting its time.

Thread.yield();
}

// Replace the future object with the obtained result.
resultMap.put(key, future.get());
}

}
}

IMPORTANT NOTE — If a thread doesn’t need CPU, it is always a good idea to pass the control to the other threads so that CPU time is effectively utilized.

— — —

Notes — Thread Synchronization

Thread Synchronization

Thread synchronization is used to solve concurrency problems that exist in parallel processing. Concurrency problem exist when more than one thread is accessing the same shared resource.

E.g.

1) More than one transaction is being performed on the same account

2) Multiple resources are booking tickets for the same train from different locations. etc.

Synchronization in Java Threads

It can be achieved through

1) Synchronized methods

2) Synchronized block

3) Locks discussed later.

synchronized method

Consider the class Sample

class Sample {
synchronized void f() {...}
}

Consider Three threads T1, T2, T3 and two objects for Sample they are A,B.

T1 ……….A.f(); locks A and proceeds

T2 ……….B.f(); locks B and proceeds

T3 ……….A.f(); wait till T1 unlocks A.

To run a synchronized method object must be locked.

synchronized block

When synchronization is not required for the entire method i.e only certain part of the code must be synchronized then we use synchronized block.

synchronized(object){
// operations over the object
}

The above code is executed only after obtaining lock over the object.

Thread Safe Code or Re-entrant code:-

When a code block is safe from concurrency problems then the code is referred as thread safe or re-entrant.In case of the below operation incr() operation is considered as thread safe or re-entrant.

Example -

In the below example try removing synchronized keyword before the incr() operation and check the result. You will find inconsistent result. By making the method synchronized, we are forcing the thread to lock the object before performing the incr() operation. Though control is intentionally passed to other thread, other thread won’t be able to proceed with the operation as it need to first lock (obj) before proceeding forward.

i.e. lets assume t1 locks obj then t2 should wait till t1 releases the lock, hence object is modified by only one thread at a time and you will see consistent results.

class Sample {
private int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public synchronized void incr() {
int y = getX();
y++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
setX(y);
}
}

class MyThread extends Thread {
Sample obj;
public MyThread(Sample obj) {
this.obj = obj;
}
public void run() {
obj.incr();
}
}

class Main {
public static void main(String[] args) {
Sample obj = new Sample();
obj.setX(10);

MyThread t1 = new MyThread(obj);
MyThread t2 = new MyThread(obj);

t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(obj.getX());
}
}

Answer is NO. Because we made it synchronized we may think that it is thread-safe but it is not. And it is because of the static variable a. Lets assume that there are two objects for Sample, in that case both of them will share the same copy of a because it is a class member, where as they get different copies of b, because b is non static i.e. the instance member and each instance will get a separate copy of b.

Assume Thread 1 invoked the increment method over the first object and Thread 2 invoked the increment method over the second object. Because the increment() method is non-static and it is synchronized, object should be locked before getting into the method.

Here Thread1 locks the first object and gets in and also Thread 2 locks the second object and gets in, because both are different objects and hence both the thread acquire locks and they both proceed with the operation.

You can see that b++ is not having any issues, because both the threads are operating on different copies of b, but what about a++ it is still not thread safe.

— —

Notes — Deadlocks and solution with lock sequencing

Deadlock -

Two threads are said to be in a deadlock when both the threads are circularly waiting for a lock over the object and hence they both get into a situation where they can not proceed with the execution.

Assume that for a writer to write lock should be acquired on both the Book and the Pen. And also assume there is only one instance of the book and the pen is available. In that case assume that writer1 acquires lock over the book object and writer 2 acquires lock over the pen object. Now writer1 is waiting for the pen and writer2 is waiting for the book, and they both are deadlocked.

class Writer1 extends Thread {
Object book;
Object pen;

public Writer1(Object book, Object pen) {
this.book = book;
this.pen = pen;
}

@Override
public void run() {
synchronized (book) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (pen) {
System.out.println("Writer 1 writing ..");
}
}
}
}

class Writer2 extends Thread {
Object book;
Object pen;

public Writer2(Object book, Object pen) {
this.book = book;
this.pen = pen;
}

@Override
public void run() {
synchronized (pen) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (book) {
System.out.println("Writer 2 writing ..");
}
}
}
}


public class Main {
public static void main(String[] args) {
Object book = new Object();
Object pen = new Object();
new Writer1(book, pen).start();
new Writer2(book, pen).start();
System.out.println("Main is done");
}
}

Deadlock arises

Solution — Lock sequencing is one possible solution for deadlock avoidance. Adjust the lock sequence for Writer2

class Writer2 extends Thread {
Object book;
Object pen;

public Writer2(Object book, Object pen) {
this.book = book;
this.pen = pen;
}

@Override
public void run() {
synchronized (book) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (pen) {
System.out.println("Writer 2 writing ..");
}
}
}
}

— — — — -

Notes — Reentrant Locks

Read/Write Lock -

Read/Write Lock gives us more flexibility during locking and unlocking. Based on the type of operation being performed over the object we can segregate the locks into

1) readLock

2) writeLock

readLock allows us to lock the object for read operation, and the interesting point is that the read operation can be shared i.e if two threads are waiting for readLock then both of them can proceed forward with the operation as read operation doesn’t change the data.

Where as writeLock is mutually exclusive i.e. if a writeLock is accepted then all the other lock requests should wait till the thread that owns the lock releases it.

For example let us assume the following chronologically ordered lock requests

T1 -> lock.readLock();

T2 -> lock.readLock();

T3 -> lock.readLock();

T4 -> lock.writeLock();

T5 -> lock.readLock();

Here T1, T2, T3 can share the readLock and proceed forward with the operation. Where T4 should wait till T1, T2 and T3 unlocks.

Why T5 is waiting ?

Because writeLock is requested by T4 before its request and hence all subsequent requests to read/write locks should wait.

This is in contrast to synchronized methods/blocks because for synchronized method/block there is no segregation of read and write operations. Object is locked no matter whether it is read or write.

Caution — It is always better to put the unlock operation in finally, as you need to unlock irrespective of exceptions.

Example -

Example is just for demo, hence lock/unlock operations are kept in incr() method itself. They can be added to getX() and setX() operations as well.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class Sample {
private int x;
ReadWriteLock rw_lock = new ReentrantReadWriteLock();

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public void incr() {
Lock lock = rw_lock.writeLock();
lock.lock();

try {
int y = getX();
y++;
try {
Thread.sleep(1);
} catch (Exception e) {
}
} finally {
lock.unlock();
}
}
}

class MyThread extends Thread {
Sample obj;
public MyThread(Sample obj) {
this.obj = obj;
}
public void run() {
obj.incr();
}
}

public class Main {
public static void main(String[] args) {
Sample obj = new Sample();
obj.setX(10);

MyThread t1 = new MyThread(obj);
MyThread t2 = new MyThread(obj);

t1.start();
t2.start();

try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( obj.getX() );
}
}

Notes — Producer and Consumer Problem

Thread signalling using wait() and notify() -

Threads can signal each other using the wait and notify methods. wait(), notify() and notifyAll() are the methods of the class Object and hence they are part of all the objects. Just think about a scenario where one thread waits for a signal from some other thread in order to proceed with the execution.

wait() method Releases the lock over the object and takes the thread to WAITING state. And the thread remains in that state until some other thread calls the notify() method over the same object. Once notify() is invoked it ends the wait for one single thread and takes the thread to BLOCKED state where the thread remains in that state till the lock is obtained. wait() only returns after obtaining the lock.

wait(long millis) slightly differs, as it takes thread to TIMED_WAITING and waits only for the specified duration.

notify() — notifies one single thread where as notifyAll() notifies all the threads waiting for the signal.

Note — In order to call the wait and notify methods the corresponding thread should hold the lock on the object using synchronized method or block.

Producer and Consumer Problem -

Here the problem that we are dealing with is the slow consumer, problem occurs when the producer produces the messages at a faster rate than the consumer can consume.

How to solve this problem?

There are different ways of solving it, one approach is to ask the producer to wait till the message is consumed.

import java.util.ArrayList;
import java.util.List;

class MessageQueue {

List<String> messages;
int limit;

public MessageQueue(int limit) {
messages = new ArrayList<String>();
this.limit = limit;
}

public boolean isFull() {
return messages.size() == limit;
}

public boolean isEmpty() {
return messages.size() == 0;
}

public synchronized void enqueue(String msg) {
while (isFull()) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
messages.add(msg);
this.notify();
}

public synchronized String dequeue() {
while (isEmpty()) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
String message = messages.remove(0);
this.notify();
return message;
}

}

class ProducerThread extends Thread {
MessageQueue queue;

public ProducerThread(MessageQueue queue) {
this.queue = queue;
}

@Override
public void run() {
for (int i = 1; i <= 10; i++) {
String msg = "Hello-" + i;
queue.enqueue(msg);
System.out.println("Produced - " + msg);
}
}
}

class ConsumerThread extends Thread {
MessageQueue queue;

public ConsumerThread(MessageQueue queue) {
this.queue = queue;
}

@Override
public void run() {
for (int i = 1; i <= 10; i++) {
String message = queue.dequeue();
System.out.println("Consumed - " + message);
}
}
}

public class Main {
public static void main(String[] args) throws InterruptedException {
MessageQueue queue = new MessageQueue(1);
new ProducerThread(queue).start();
new ConsumerThread(queue).start();
}
}

Explanation -

Here we have a class MessageQueue which acts as a buffer between producer thread and the consumer thread. And the buffer is bounded, so we can set the limit during object creation.

isFull() returns true if messages size reaches the limit.

isEmpty() return true if messages size is equal to zero.

enqueue() — Here it is invoked only by ProducerThread; it adds (at the end) the message to the messages array if it is not full, if full it calls the wait() over the queue object (this) till the consumer consumes the message and notifies it. Also once it adds the message to the queue, it calls the notify() in order to end the consumer’s wait() if any.

dequeue() — Here it is invoked only by ConsumerThread; it removes the first element and returns it. If the queue is empty it calls the wait() over the queue object till the producer produces the message. Once it consumes the message it calls the notify() to end the producer’s wait() if any.

main() initiates these operations, by creating the MessageQueue object and passing it to both the producer and consumer threads.

— —

--

--