Java Multithreading
Java Multithreading
By Fayaz
Process Vs Threads:
Time Slicing algorithm: This is the algorithm used by single processor to handle multiple threads.
When we have multiple processors or cores then all the threads can be executed in parallel manner.
t1.start();
Thread.sleep(500);
t1.stop();
Thread.interrupt():
Instead of stopping a thread, we should rely on cooperative thread interruption.
In simple words, we should ask a thread to stop itself in the right moment by using Thread#interrupt
var threadSafeCounter = new ThreadSafeCounter();
@Override
public void run() {
partialSum = 0;
int total = 0;
int numOfProcessors =
Runtime.getRuntime().availableProcessors();
start = System.currentTimeMillis();
}
}
Daemon Threads:
Background threads that do not prevent the application from exiting if the main thread terminates.
In Java, daemon threads are low-priority threads that run in the background to perform tasks such as
garbage collection or provide services to user threads. The life of a daemon thread depends on the
mercy of user threads, meaning that when all user threads finish their execution, the Java Virtual
Machine (JVM) automatically terminates the daemon thread.
Scenario1:
Background tasks, that should not block our application from terminating
Scenario2:
Code in a worker thread is not under our control, and we don’t want it to block our application from
terminating.
class GFG
{
public static void main (String[] args)
{
// thread t1 starts
t1.start();
catch(Exception ex)
{
System.out.println("Exception has " +
"been caught" + ex);
}
// t2 starts
t2.start();
catch(Exception ex)
{
System.out.println("Exception has been" +
" caught" + ex);
}
t3.start();
}
}
yield() Method:
If any thread executes the yield method, the thread scheduler checks if there is any thread with the
same or high priority as this thread. If the processor finds any thread with higher or same priority then it
will move the current thread to Ready/Runnable state and give the processor to another thread and if
not – the current thread will keep executing.
The process of testing a condition repeatedly till it becomes true is known as polling. Polling is usually
implemented with the help of loops to check whether a particular condition is true or not. If it is true, a
certain action is taken. This wastes many CPU cycles and makes the implementation inefficient.
To avoid polling, Java uses three methods, namely, wait(), notify(), and notifyAll(). All these methods
belong to object class as final so that all classes have them. They must be used within a synchronized
block only.
• wait(): It tells the calling thread to give up the lock and go to sleep until some other thread
enters the same monitor and calls notify().
• notify(): It wakes up one single thread called wait() on the same object. It should be noted
that calling notify() does not give up a lock on a resource.
• notifyAll(): It wakes up all the threads called wait() on the same object.
import java.util.Scanner;
// t1 finishes before t2
t1.join();
t2.join();
}
// Sleeps for some time and waits for a key press. After key
// is pressed, it notifies produce().
public void consume()throws InterruptedException
{
// this makes the produce thread to run first.
Thread.sleep(1000);
Scanner s = new Scanner(System.in);
// Sleep
Thread.sleep(2000);
}
}
}
}
Race conditions can be avoided by proper thread synchronization in critical sections. Thread
synchronization can be achieved using a synchronized block of Java code. Thread synchronization can
also be achieved using other synchronization constructs like locks or atomic variables like
java.util.concurrent.atomic.AtomicInteger
Data Race:
A data race occurs when: two or more threads in a single process access the same memory location
concurrently, and. at least one of the accesses is for writing, and. the threads are not using any exclusive
locks to control their accesses to that memory.
Solution: Synchronization or declaration of variables using volatile keyword
Synchronized:
• Locking mechanism
• Used to restrict access to a critical section or entire method to a single thread at a time
• synchronized is keyword used to guard a method or code block. By making method as
synchronized has two effects:
• First, it is not possible for two invocations of synchronized methods on the same object to
interleave.
• When one thread is executing a synchronized method for an object, all other threads that
invoke synchronized methods for the same object block (suspend execution) until the first
thread is done with the object.
• Second, when a synchronized method exits, it automatically establishes a happens-before
relationship with any subsequent invocation of a synchronized method for the same object. This
guarantees that changes to the state of the object are visible to all threads.
When to use: Multiple threads can read and modify data. Your business logic not only update the data
but also executes atomic operations.
incrementingThread.join();
decrementingThread.join();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
inventoryCounter.decrement();
}
}
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
inventoryCounter.increment();
}
}
}
private static class InventoryCounter {
private int items = 0;
Reentrant Lock:
As the name says, ReentrantLock allows threads to enter into the lock on a resource more than once.
When the thread first enters into the lock, a hold count is set to one. Before unlocking the thread can re-
enter into lock again and every time hold count is incremented by one. For every unlocks request, hold
count is decremented by one and when hold count is 0, the resource is unlocked.
while (true) {
if (fromAcct.lock.tryLock()) {
try {
if (toAcct.lock.tryLock()) {
try {
if (fromAcct.getBalance().compareTo(amount)
< 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
return true;
}
} finally {
toAcct.lock.unlock();
}
}
} finally {
fromAcct.lock.unlock();
}
}
if (System.nanoTime() < stopTime)
return false;
NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
}
}
class Worker {
t1.start();
t2.start();
}
}
}
}
int ticketsAvailable=1;
Lock lock;
public MyRunnable(Lock lock) {
this.lock=lock;
}
lock.lock();
if(ticketsAvailable>0){
System.out.println("Booking ticket for : "+
Thread.currentThread().getName());
3) One more worth noting difference between ReentrantLock and synchronized keyword in Java is,
ability to interrupt Thread while waiting for Lock.
In case of synchronized keyword, a thread can be blocked waiting for lock,
for an indefinite period of time and there was no way to control that.
ReentrantLock provides a method called lockInterruptibly(), which can be used to interrupt thread
when it is waiting for lock.
Similarly tryLock() with timeout can be used to timeout if lock is not available in certain time period.
4) ReentrantLock also provides convenient method to get List of all threads waiting for lock.
9. Synchronization constructs:
Semaphore:
A semaphore controls access to a shared resource through the use of a counter. If the counter is greater
than zero, then access is allowed. If it is zero, then access is denied. What the counter is counting are
permits that allow access to the shared resource. Thus, to access the resource, a thread must be granted
a permit from the semaphore.
Semaphore in Java is a thread synchronization construct that avoids missed signals between threads by
sending signals to the threads and protecting critical sections. With the use of counters, Semaphore
manages access to the shared resources.
We can use a semaphore to lock access to a resource, each thread that wants to use that resource must
first call acquire( ) before accessing the resource to acquire the lock. When the thread is done with the
resource, it must call release( ) to release lock. Here is an example that demonstrate this:
/**
*
* - semaphore maintains a set of permits
* - acquire() -> if a permit is available then takes it
* - release() -> adds a permit
*
* Semaphore just keeps a count of the number available
* new Semaphore(int permits, boolean fair) !!!
*/
enum Downloader {
INSTANCE;
try {
semaphore.acquire();
download();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
ExecutorService executorService =
Executors.newCachedThreadPool();
for(int i=0;i<12;i++) {
executorService.execute(new Runnable() {
public void run() {
Downloader.INSTANCE.downloadData();
}
});
}
}
}
// java program to demonstrate
// use of semaphores Locks
import java.util.concurrent.*;
@Override
public void run() {
// run by thread A
if(this.getName().equals("A"))
{
System.out.println("Starting " + threadName);
try
{
// First, get a permit.
System.out.println(threadName + " is waiting
for a permit.");
// run by thread B
else
{
System.out.println("Starting " + threadName);
try
{
// First, get a permit.
System.out.println(threadName + " is waiting
for a permit.");
// Driver class
public class SemaphoreDemo
{
public static void main(String args[]) throws InterruptedException
{
// creating a Semaphore object
// with number of permits 1
Semaphore sem = new Semaphore(1);
CountDownLatch is a construct that a thread waits on while other threads count down on the latch until
it reaches zero. You use the Future to obtain a result from a submitted Callable, and you use a
CountDownLatch when you want to be notified when all threads have completed -- the two are not
directly related, and you use one, the other or both together when and where needed.
CyclicBarrier is a synchronizer that allows a set of threads to wait for each other to reach a common
execution point, also called a barrier.
CyclicBarriers are used in programs in which we have a fixed number of threads that must wait for each
other to reach a common point before continuing execution.
The barrier is called cyclic because it can be re-used after the waiting threads are released.
CyclicBarier waits for certain number of threads while CountDownLatch waits for certain number of
events (one thread could call CountDownLatch.countDown() several times). CountDownLatch cannot be
reused once opened. Also the thread which calls CountDownLatch.countDown() only signals that it
finished some stuff. It doesn't block (and in CyclicBarrier.await() is blocking method) and could continue
to do some other stuff.
The CyclicBarrier uses an all-or-none breakage model for failed synchronization attempts: If a thread
leaves a barrier point prematurely because of interruption, failure, or timeout, all other threads waiting
at that barrier point will also leave abnormally via BrokenBarrierException (or InterruptedException if
they too were interrupted at about the same time).
cyclicBarrier = new CyclicBarrier(NUM_WORKERS, new AggregatorThread());
class NumberCruncherThread implements Runnable {
@Override
public void run() {
String thisThreadName = Thread.currentThread().getName();
List<Integer> partialResult = new ArrayList<>();
partialResults.add(partialResult);
try {
System.out.println(thisThreadName
+ " waiting for others to reach barrier.");
cyclicBarrier.await();
} catch (InterruptedException e) {
// ...
} catch (BrokenBarrierException e) {
// ...
}
}
}
// Previous code
Volatile: volatile is a keyword. volatile forces all threads to get latest value of the variable from main
memory instead of cache.
No locking is required to access volatile variables. All threads can access volatile variable value at same
time.
Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile
variable establishes
a happens-before relationship with subsequent reads of that same variable.
This means that changes to a volatile variable are always visible to other threads.
What's more, it also means that when a thread reads a volatile variable,
it sees not just the latest change to the volatile, but also the side effects of the code that led up the
change.
When to use: One thread modifies the data and other threads have to read latest value of data.
Other threads will take some action but they won't update data.
https://www.callicoder.com/java-locks-and-atomic-variables-tutorial/
https://www.callicoder.com/java-concurrency-issues-and-thread-synchronization/
Atomic Variables:
If two threads try to get and update the value at the same time, it may result in lost updates.
One of the ways to manage access to an object is to use locks. This can be achieved by using the
synchronized keyword in the increment method signature.
Using locks solves the problem. However, the performance takes a hit.
• When multiple threads attempt to acquire a lock, one of them wins, while the rest of the
threads are either blocked or suspended.
• The process of suspending and then resuming a thread is very expensive and affects the overall
efficiency of the system
There is a branch of research focused on creating non-blocking algorithms for concurrent environments.
These algorithms exploit low-level atomic machine instructions such as compare-and-swap (CAS), to
ensure data integrity. A typical CAS operation works on three operands:
The most commonly used atomic variable classes in Java are AtomicInteger, AtomicLong,
AtomicBoolean, and AtomicReference. These classes represent an int, long, boolean, and object
reference respectively which can be atomically updated
import java.util.concurrent.atomic.AtomicInteger;
class threadCount extends Thread {
//creating an instance of Atomic Variable Class
AtomicInteger total;
threadCount(){
total = new AtomicInteger();
}
public void run(){
int max = 100000;
for (int i = 0; i < max; i++) {
//Calling the addAndGet function
total.addAndGet(1);
}
}
}
}
}
Thread.sleep(30000);
//shut down the pool
executorPool.shutdown();
//shut down the monitor thread
Thread.sleep(5000);
monitor.shutdown();
}
}
Notice that while initializing the ThreadPoolExecutor, we are keeping initial pool size as 2, maximum
pool size to 4 and work queue size as 2. So if there are 4 running tasks and more tasks are submitted,
the work queue will hold only 2 of them and the rest of them will be handled by
RejectedExecutionHandlerImpl. Here is the output of the above program that confirms the above
statement.
Executor Service: Asynchronous task execution engine. Java5
This enabled coarse-grained task based parallelism in java
The main difference between Executor, ExecutorService, and Executors class is that Executor is the core
interface which is an abstraction for parallel execution. It separates tasks from execution, this is
different from java.lang.Thread class which combines both task and its execution.
ExecutorService is an extension of the Executor interface and provides a facility for returning a Future
object and terminate, or shut down the thread pool. Once the shutdown is called, the thread pool will
not accept new tasks but complete any pending task. It also provides a submit() method which
extends Executor.execute() method and returns a Future.
The Future object provides the facility of asynchronous execution, which means you don't need to wait
until the execution finishes, you can just submit the task and go around, come back and check if the
Future object has the result, if the execution is completed then it would have a result which you can
access by using the Future.get() method. Just remember that this method is a blocking method i.e. it will
wait until execution finish and the result is available if it's not finished already.
By using the Future object returned by ExecutorService.submit() method, you can also cancel the
execution if you are not interested anymore. It provides a cancel() method to cancel any pending
execution.
Another important difference between ExecutorService and Executor is that Executor defines execute()
method which accepts an object of the Runnable interface, while submit() method can accept objects of
both Runnable and Callable interfaces.
Executors is a utility class similar to Collections, which provides factory methods to create different
types of thread pools
Runnable r=
()->{
System.out.println("test");
};
ExecutorService es=Executors.newFixedThreadPool(10);
s.execute(r);//s.submit(r);
Callable<String> c=()->{
System.out.println("Callabe test");
return "test";
};
List<Callable<String>> al=new ArrayList<>();
al.add(c);
al.add(c);
es.invokeAll(al);
String result = executorService.invokeAny(callableTasks);
However, submit(Callable<T>) returns a Future object which allows a way for you to programatically
cancel the running thread later as well as get the T that is returned when the Callable completes
ExecutorService es = Executors.newCachedThreadPool();
- going to return an executorService that can dynamically reuse threads
- before starting a job -> it going to check whether there are any threads that
finished the job...reuse them
- if there are no waiting threads -> it is going to create another one
- good for the processor ... effective solution !!!
ExecutorService es = Executors.newFixedThreadPool(N);
- maximize the number of threads
- if we want to start a job -> if all the threads are busy, we have to wait for one
to terminate
ExecutorService es = Executors.newSingleThreadExecutor();
It uses a single thread for the job
Fork-join framework:java7
Executor Framework was enhanced to support fork-join tasks, which will run by a special kind of
executor service known as a fork-join pool.
public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);
With ForkJoinPool’s constructors, we can create a custom thread pool with a specific level of parallelism,
thread factory and exception handler. Here the pool has a parallelism level of 2. This means that pool
will use two processor cores.
To achieve Data parallelism: task divided into multiple subtasks until it reaches its least possible size and
execute those tasks in parallel.
Work-Stealing Algorithm: Simply put, free threads try to “steal” work from deques of busy threads.
ForkJoinTask<V>
ForkJoinTask is the base type for tasks executed inside ForkJoinPool. In practice, one of its two
subclasses should be extended: the RecursiveAction for void tasks and the RecursiveTask<V> for tasks
that return a value. They both have an abstract method compute() in which the task’s logic is defined.
@Override
protected Integer compute() {
if (arr.length > THRESHOLD) {
return ForkJoinTask.invokeAll(createSubtasks())
.stream()
.mapToInt(ForkJoinTask::join)
.sum();
} else {
return processing(arr);
}
}
@Override
protected void compute() {
if (workload.length() > THRESHOLD) {
ForkJoinTask.invokeAll(createSubtasks());
} else {
processing(workload);
}
}
subtasks.add(new CustomRecursiveAction(partOne));
subtasks.add(new CustomRecursiveAction(partTwo));
return subtasks;
}
return max;
}
}
invokeAll(task1, task2);
for(int i=lowIndex+1;i<highIndex;++i)
if(nums[i] > max)
max = nums[i];
return max;
}
}
class ParallelMaxTask{
for(int i=0;i<nums.length;++i)
nums[i] = random.nextInt(100000);
return nums;
}
Future: synchronous and blocking programming, because the get() method blocks the main thread
while(!result.isDone()) {
System.out.println("Main thread is waiting for the result ...");
}
// blocks the main thread !!!
String res = result.get();
System.out.println(res);
Asynchronous reactive functional programming API. It created to solve the limitations of Future API.
Java 8 introduced the CompletableFuture class. Along with the Future interface, it also implemented the
CompletionStage interface.
This interface defines the contract for an asynchronous computation step that we can combine with
other steps.
Combining Futures
The best part of the CompletableFuture API is the ability to combine CompletableFuture instances in a
chain of computation steps.
2.CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));
completableFuture.get();
System.out.println("Execution start");
// Block and get the result of the future.
System.out.println(greetingFuture.get()); // Hello Fayaz
System.out.println("Execution ends");
thenApply():
You can also write a sequence of transformations on the CompletableFuture by attaching a series of
thenApply() callback methods. The result of one thenApply() method is passed to the next in the series –
CompletableFuture<String> welcomeText = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Fayaz";
}).thenApply(name -> {
return "Hello " + name;
}).thenApply(greeting -> {
return greeting + ", Welcome to the Fayaz Blog";
});
System.out.println("Execution start");
// Block and get the result of the future.
System.out.println(welcomeText.get()); // Hello Fayaz, Welcome to the Fayaz Blog
System.out.println("Execution ends");
In earlier examples, the Supplier function passed to thenApply() callback would return a simple value but
in this case, it is returning a CompletableFuture. Therefore, the final result in the above case is a nested
CompletableFuture.
If you want the final result to be a top-level Future, use thenCompose() method instead -
CompletableFuture<Double> result = getUserDetail(userId)
.thenCompose(user -> getCreditRating(user));
System.out.println("Retrieving height.");
CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 177.8;
});
System.out.println("Calculating BMI.");
CompletableFuture<Double> combinedFuture = weightInKgFuture
.thenCombine(heightInCmFuture, (weightInKg, heightInCm) -> {
Double heightInMeter = heightInCm/100;
return weightInKg/(heightInMeter*heightInMeter);
});
CompletableFuture<String> future1
= CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2
= CompletableFuture.supplyAsync(() -> "Beautiful");
CompletableFuture<String> future3
= CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<Void> combinedFuture
= CompletableFuture.allOf(future1, future2, future3);
// ...
combinedFuture.get();
Error Handling:
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> {
if (name == null) {
throw new RuntimeException("Computation error!");
}
return "Hello, " + name;
}).handle((s, t) -> s != null ? s : "Hello, Stranger!");
#State.java
public enum State {
LEFT, RIGHT;
}
#Constants.java
class Constants {
private Constants(){
}
//ChopStick.java
package threads.PhilosopherProblem;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
return false;
}
@Override
public String toString() {
return "Chopstick-"+this.id;
}
}
//Philosopher.java
class Philosopher implements Runnable {
@Override
public void run() {
try{
while( !isFull ){
think();
leftChopStick.putDown(this, State.LEFT);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
@Override
public String toString() {
return "Philosopher-"+this.id;
}
}
//Main class
public class App {
try{
philosophers = new
Philosopher[Constants.NUMBER_OF_PHILOSOPHERS];
ChopStick[] chopSticks = new
ChopStick[Constants.NUMBER_OF_PHILOSOPHERS];
for(int i=0;i<Constants.NUMBER_OF_CHOPSTICKS;i++){
chopSticks[i] = new ChopStick(i);
}
executorService =
Executors.newFixedThreadPool(Constants.NUMBER_OF_PHILOSOPHERS);
for(int i=0;i<Constants.NUMBER_OF_PHILOSOPHERS;i++){
//philosophers[i] = new Philosopher(i, chopSticks[i],
chopSticks[(i+1) % Constants.NUMBER_OF_PHILOSOPHERS]);
philosophers[i] = new Philosopher(i, chopSticks[i],
chopSticks[(i)]);
executorService.execute(philosophers[i]);
}
Thread.sleep(Constants.SIMULATION_RUNNING_TIME);
executorService.shutdown();
while(!executorService.isTerminated()){
Thread.sleep(1000);
}
}
}
ParalellStreams:
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
public class ParalellTest {
boolean isPrime=false;
//if(num==1)isPrime= false;
if(num==2)isPrime= true;
//if(num%2==0)isPrime= false;
start = System.currentTimeMillis();
System.out.println(parallelSum(2000000000));
System.out.println("Time taken parallel: " + (System.currentTimeMillis() -
start));
}
Virtual Threads:
A Virtual Thread is also an instance of java.lang.Thread, but it runs Java code on the same OS thread and
shares it effectively, which makes the number of virtual threads can be much larger than the number of
OS threads.
Because the number of OS threads does not limit virtual threads, we can quickly increase the concurrent
requests to achieve higher throughput.
one standard Java (platform) thread is always associated with a single OS thread while the thread is
running .
IT DOES NOT MATTER IF THE THREAD IS BLOCKED, THE UNDRLYING OS THREAD IS ASSIGNED TO THAT
THREAD !!!
this is the main disadvantage of standard (platform) threads and this is why they are not scalable
virtual threads are so lightweight that there is no need to reuse them (no pooling required). virtual
threads are cheap to block – no OS thread is blocked under the hood because the JVM reuses the carrier
thread. use very limited resources (such as memory) because it is used for limited operations (single
HTTP request etc.)
System.out.println(res);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// wait
}
Structured Concurrency:
public class LongProcessFail implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("Starting thread " + result);
Thread.sleep(Duration.ofSeconds(timeToSleep));
if(fail) {
System.out.println("Failure in child thread: " + result);
throw new RuntimeException("Error");
}
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
// we do not pool virtual threads: we create new ones for every task
// and we dispose them after they finished
try(var scope = new StructuredTaskScope<String>()) {
if(res1.state() == State.SUCCESS)
System.out.println(res1.get());
if(res2.state() == State.SUCCESS)
System.out.println(res2.get());
// we do not pool virtual threads: we create new ones for every task
// and we dispose them after they finished
try(var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
try {
result = scope.result();
System.out.println(result);
} catch (ExecutionException e) {
System.out.println("There is no solution...");
}
}
References:
https://javarevisited.blogspot.com/2017/02/difference-between-executor-executorservice-and-
executors-in-java.html#ixzz7w2iYxUc6
https://www.callicoder.com/java-8-completablefuture-tutorial/
https://www.callicoder.com/java-locks-and-atomic-variables-tutorial/
https://www.callicoder.com/java-concurrency-issues-and-thread-synchronization/
https://www.javamadesoeasy.com/