0% found this document useful (0 votes)
32 views63 pages

Java Multithreading

Uploaded by

pt17.tiwari
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
32 views63 pages

Java Multithreading

Uploaded by

pt17.tiwari
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 63

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.

Why we need Threads:


1. Concurrency – Multitasking: Achieved by multitasking between threads.

2.Performance (Parallelism) Completing a complex task much faster.

What is Multitasking in Java?


Multitasking is the process that lets users perform multiples tasks at the same time. There are two
ways to enable multitasking in Java:
Process-based multitasking: The processes in this type of multitasking are heavy and a lot of time is
consumed. This is because the program takes a long time to switch between different processes.
Thread-based multi-tasking: Threads are light-weight compared to process-based multi tasking, and
the time taken to switch between them is shorter.
How to create Threads:
There are two ways to create a thread:
• By extending Thread class
• By implementing Runnable interface.

class Multi extends Thread{


public void run(){
System.out.println("thread is running...");
}
public static void main(String args[]){
Multi t1=new Multi();
t1.start();
}
}

class Multi3 implements Runnable{


public void run(){
System.out.println("thread is running...");
}

public static void main(String args[]){


Multi3 m1=new Multi3();
Thread t1 =new Thread(m1); // Using the constructor Thread(Runnable r)
t1.start();
}
}

Thread Life Cycle:


Threads exist in several states. Following are those states:
• New – When we create an instance of Thread class, a thread is in a new state.
• Runnable – The Java thread is in running state.
• Suspended – A running thread can be suspended, which temporarily suspends its activity. A
suspended thread can then be resumed, allowing it to pick up where it left off.
• Blocked – A java thread can be blocked when waiting for a resource.
• Terminated – A thread can be terminated, which halts its execution immediately at any given
time. Once a thread is terminated, it cannot be resumed.
Threads Termination:
Thread.sleep(): not a better way
when we try to stop an existing thread, it can be in the middle of an operation that must not be
terminated immediately.
Thread.sleep:
var threadSafeCounter = new ThreadSafeCounter();

var t1 = new Thread(() -> {


while (true) {
threadSafeCounter.incrementAndGet();
}
});

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();

var t1 = new Thread(() -> {


while (!Thread.interrupted()) {
threadSafeCounter.incrementAndGet();
}
});
t1.start();
Thread.sleep(500);
t1.interrupt();

Parallel Sum using Multithreading:


public class ParallelWorker extends Thread {

private int[] nums;


private int low;
private int high;
private int partialSum;

public ParallelWorker(int[] nums, int low, int high) {


this.nums = nums;
this.low = low;
this.high = Math.min(high, nums.length);
}

public int getPartialSum() {


return partialSum;
}

@Override
public void run() {

partialSum = 0;

for (int i = low; i < high; i++) {


partialSum += nums[i];
}
}
}

public class SequentialSum {

public int sum(int[] nums) {

int total = 0;

for (int i = 0; i < nums.length; ++i) {


total += nums[i];
}
return total;
}
}

public class ParallelSumApp {

public static void main(String[] args) {

Random random = new Random();


SequentialSum sequentialSum = new SequentialSum();

int numOfProcessors =
Runtime.getRuntime().availableProcessors();

int[] nums = new int[100000000];

for (int i = 0; i < nums.length; i++) {


nums[i] = random.nextInt(101) + 1; // 1..100
}

long start = System.currentTimeMillis();

System.out.println("Sum is: " + sequentialSum.sum(nums));

System.out.println("Single: " + (System.currentTimeMillis() -


start)+"ms"); // Single: 44

start = System.currentTimeMillis();

ParallelSum parallelSum = new ParallelSum(numOfProcessors);


System.out.println("Sum is: " +parallelSum.parallelSum(nums));

System.out.println("Parallel: " + (System.currentTimeMillis() -


start)+ "ms"); // Parallel: 25

}
}

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.

Threads coordination using Thread. join() method.


java.lang.Thread class provides the join() method which allows one thread to wait until another thread
completes its execution

class GFG
{
public static void main (String[] args)
{

// creating two threads


ThreadJoining t1 = new ThreadJoining();
ThreadJoining t2 = new ThreadJoining();
ThreadJoining t3 = new ThreadJoining();

// thread t1 starts
t1.start();

// starts second thread after when


// first thread t1 has died.
try
{
System.out.println("Current Thread: "
+ Thread.currentThread().getName());
t1.join();
}

catch(Exception ex)
{
System.out.println("Exception has " +
"been caught" + ex);
}

// t2 starts
t2.start();

// starts t3 after when thread t2 has died.


try
{
System.out.println("Current Thread: "
+ Thread.currentThread().getName());
t2.join();
}

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.

Inter-thread Communication in Java:


Inter-thread communication in Java is a mechanism in which a thread is paused running in its critical
section and another thread is allowed to enter (or lock) in the same critical section to be executed.

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;

public class Threadexample


{
public static void main(String[] args) throws InterruptedException
{
final PC pc = new PC();

// Create a thread object that calls pc.produce()


Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
pc.produce();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
});

// Create another thread object that calls


// pc.consume()
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
pc.consume();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
});

// Start both threads


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

// t1 finishes before t2
t1.join();
t2.join();
}

// PC (Produce Consumer) class with produce() and


// consume() methods.
public static class PC
{
// Prints a string and waits for consume()
public void produce()throws InterruptedException
{
// synchronized block ensures only one thread
// running at a time.
synchronized(this)
{
System.out.println("producer thread running");

// releases the lock on shared resource


wait();

// and waits till some other method invokes


notify().
System.out.println("Resumed");
}
}

// 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);

// synchronized block ensures only one thread


// running at a time.
synchronized(this)
{
System.out.println("Waiting for return key.");
s.nextLine();
System.out.println("Return key pressed");

// notifies the produce thread that it


// can wake up.
notify();

// Sleep
Thread.sleep(2000);
}
}
}
}

Synchronization between multiple threads:


Critical section:
A race condition is a concurrency problem that may occur inside a critical section. A critical section is a
section of code that is executed by multiple threads and where the sequence of execution for the
threads makes a difference in the result of the concurrent execution of the critical section

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

public class TwoSums {


private int sum1 = 0;
private int sum2 = 0;

public void add(int val1, int val2){


synchronized(this){
this.sum1 += val1;
this.sum2 += val2;
}
}
}

public class TwoSums {


private int sum1 = 0;
private int sum2 = 0;

private Integer sum1Lock = new Integer(1);


private Integer sum2Lock = new Integer(2);

public void add(int val1, int val2){


synchronized(this.sum1Lock){
this.sum1 += val1;
}
synchronized(this.sum2Lock){
this.sum2 += val2;
}
}
}
Race condition:
• Condition when multiple threads are accessing a shared resource.
• At least one thread modifying the resource.
• The timing of threads scheduling may cause incorrect results.
• The core of the problem is non atomic operations performed on the shared resource.

Solution: Synchronization or declaration of variables using volatile keyword

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.

public class Main {


public static void main(String[] args) throws InterruptedException {
InventoryCounter inventoryCounter = new InventoryCounter();
IncrementingThread incrementingThread = new
IncrementingThread(inventoryCounter);
DecrementingThread decrementingThread = new
DecrementingThread(inventoryCounter);
incrementingThread.start();
decrementingThread.start();

incrementingThread.join();
decrementingThread.join();

System.out.println("We currently have " + inventoryCounter.getItems()


+ " items");
}

public static class DecrementingThread extends Thread {

private InventoryCounter inventoryCounter;

public DecrementingThread(InventoryCounter inventoryCounter) {


this.inventoryCounter = inventoryCounter;
}

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
inventoryCounter.decrement();
}
}
}

public static class IncrementingThread extends Thread {

private InventoryCounter inventoryCounter;

public IncrementingThread(InventoryCounter inventoryCounter) {


this.inventoryCounter = inventoryCounter;
}

@Override
public void run() {
for (int i = 0; i < 10000; i++) {
inventoryCounter.increment();
}
}
}
private static class InventoryCounter {
private int items = 0;

Object lock = new Object();

public void increment() {


synchronized (this.lock) {
items++;
}
}

public void decrement() {


synchronized (this.lock) {
items--;
}
}

public int getItems() {


synchronized (this.lock) {
return items;
}
}
}
}

Intrinsic Lock (Monitor) Method level


public synchronized void increment() {
counter++;
}

Intrinsic Lock (Monitor) Object level


public void increment() {
synchronized(this) {
counter++;
}
}

Intrinsic Lock (Monitor) Class level


public void increment() {
synchronized(SomeClass.class) {
counter++;
}
}

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.

ReentrantLock() important Methods


• lock(): Call to the lock() method increments the hold count by 1 and gives the lock to the thread
if the shared resource is initially free.
• unlock(): Call to the unlock() method decrements the hold count by 1. When this count reaches
zero, the resource is released.
• tryLock(): If the resource is not held by any other thread, then call to tryLock() returns true and
the hold count is incremented by one. If the resource is not free, then the method returns false,
and the thread is not blocked, but exits.
• tryLock(long timeout, TimeUnit unit): As per the method, the thread waits for a certain time
period as defined by arguments of the method to acquire the lock on the resource before
exiting.
• lockInterruptibly(): This method acquires the lock if the resource is free while allowing for the
thread to be interrupted by some other thread while acquiring the resource. It means that if the
current thread is waiting for the lock but some other thread requests the lock, then the current
thread will be interrupted and return immediately without acquiring the lock.
• getHoldCount(): This method returns the count of the number of locks held on the resource.
• isHeldByCurrentThread(): This method returns true if the lock on the resource is held by the
current thread.

public void some_method(){


reentrantlock.lock();
try{
//Do some work
}
catch(Exception e){
e.printStackTrace();
}
finally{
reentrantlock.unlock();
}

public boolean transferMoney(Account fromAcct,


Account toAcct,
DollarAmount amount,
long timeout,
TimeUnit unit)
throws InsufficientFundsException, InterruptedException {
long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
long randMod = getRandomDelayModulusNanos(timeout, unit);
long stopTime = System.nanoTime() + unit.toNanos(timeout);

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);
}
}

Producer and Consumer Problem:

class Worker {

private Lock lock = new ReentrantLock();


private Condition condition = lock.newCondition();
//private List<Integer> list = new ArrayList<>();

public void produce() throws InterruptedException {


lock.lock();
System.out.println("Producer method...");
condition.await();
System.out.println("Producer method again...");
}

public void consume() throws InterruptedException {


lock.lock();
Thread.sleep(2000);
System.out.println("Consumer method...");
Thread.sleep(3000);
condition.signal();
lock.unlock();
}
}

public class App {

public static void main(String[] args) {

final Worker worker = new Worker();

Thread t1 = new Thread(new Runnable() {


public void run() {
try {
worker.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread t2 = new Thread(new Runnable() {


public void run() {
try {
worker.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

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

}
}

Passenger Ticket Booking Problem:


public class ReentrantLockTest {
public static void main(String[] args) {

Lock lock=new ReentrantLock();


MyRunnable myRunnable=new MyRunnable(lock);
new Thread(myRunnable,"Passenger1 Thread").start();
new Thread(myRunnable,"Passenger2 Thread").start();

}
}

class MyRunnable implements Runnable{

int ticketsAvailable=1;
Lock lock;
public MyRunnable(Lock lock) {
this.lock=lock;
}

public void run(){

System.out.println("Waiting to book ticket for : "+


Thread.currentThread().getName());

lock.lock();

if(ticketsAvailable>0){
System.out.println("Booking ticket for : "+
Thread.currentThread().getName());

//Let's say system takes some time in booking ticket


//(here we have taken 1 second time)
try{
Thread.sleep(1000);
}catch(Exception e){}
ticketsAvailable--;
System.out.println("Ticket BOOKED for : "+
Thread.currentThread().getName());
System.out.println("currently ticketsAvailable =
"+ticketsAvailable);
}
else{
System.out.println("Ticket NOT BOOKED for : "+
Thread.currentThread().getName());
}

lock.unlock(); //read explanation for 5sec


}
}

Intrinsic (Synchronized) and Reentrant/Explicit locks(trylock()) differences:


1) Another significant difference between ReentrantLock and synchronized keyword is fairness.
synchronized keyword doesn't support fairness. Any thread can acquire lock once released, no
preference can be specified,
on the other hand you can make ReentrantLock fair by specifying fairness property, while creating
instance of ReentrantLock. Fairness property provides lock to longest waiting thread, in case of
contention.

2) Second difference between synchronized and Reentrant lock is tryLock() method.


ReentrantLock provides convenient tryLock() method, which acquires lock only if its available
or not held by any other thread. This reduce blocking of thread waiting for lock in Java application.

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;

private Semaphore semaphore = new Semaphore(5, true);


public void downloadData() {

try {
semaphore.acquire();
download();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}

private void download() {


System.out.println("Downloading data from the web...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class App {

public static void main(String[] args) {

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.*;

//A shared resource/class.


class Shared
{
static int count = 0;
}

class MyThread extends Thread


{
Semaphore sem;
String threadName;
public MyThread(Semaphore sem, String threadName)
{
super(threadName);
this.sem = sem;
this.threadName = threadName;
}

@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.");

// acquiring the lock


sem.acquire();

System.out.println(threadName + " gets a


permit.");

// Now, accessing the shared resource.


// other waiting threads will wait, until this
// thread release the lock
for(int i=0; i < 5; i++)
{
Shared.count++;
System.out.println(threadName + ": " +
Shared.count);

// Now, allowing a context switch -- if


possible.
// for thread B to execute
Thread.sleep(10);
}
} catch (InterruptedException exc) {
System.out.println(exc);
}

// Release the permit.


System.out.println(threadName + " releases the
permit.");
sem.release();
}

// run by thread B
else
{
System.out.println("Starting " + threadName);
try
{
// First, get a permit.
System.out.println(threadName + " is waiting
for a permit.");

// acquiring the lock


sem.acquire();

System.out.println(threadName + " gets a


permit.");

// Now, accessing the shared resource.


// other waiting threads will wait, until this
// thread release the lock
for(int i=0; i < 5; i++)
{
Shared.count--;
System.out.println(threadName + ": " +
Shared.count);

// Now, allowing a context switch -- if


possible.
// for thread A to execute
Thread.sleep(10);
}
} catch (InterruptedException exc) {
System.out.println(exc);
}
// Release the permit.
System.out.println(threadName + " releases the
permit.");
sem.release();
}
}
}

// 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);

// creating two threads with name A and B


// Note that thread A will increment the count
// and thread B will decrement the count
MyThread mt1 = new MyThread(sem, "A");
MyThread mt2 = new MyThread(sem, "B");

// stating threads A and B


mt1.start();
mt2.start();

// waiting for threads A and B


mt1.join();
mt2.join();

// count will always remain 0 after


// both threads will complete their execution
System.out.println("count: " + Shared.count);
}
}

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.

CountDownLatch latch = new CountDownLatch(4);


// Creating worker threads
Worker first = new Worker(1000, latch, "WORKER-1");
Worker second = new Worker(2000, latch, "WORKER-2");
Worker third = new Worker(3000, latch, "WORKER-3");
Worker fourth = new Worker(4000, latch, "WORKER-4");
// Starting above 4 threads
first.start();
second.start();
third.start();
fourth.start();
// The main task waits for four threads
latch.await();

class Worker extends Thread {


private int delay;
private CountDownLatch latch;

public Worker(int delay, CountDownLatch latch,


String name)
{
super(name);
this.delay = delay;
this.latch = latch;
}

@Override public void run()


{
try {
Thread.sleep(delay);
latch.countDown();
System.out.println(Thread.currentThread().getName() + " finished");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}

• In a hypothetical theater(Synchronization methods)


• It is called Mutex if only one person is allowed to watch the play.
• It is called Semaphore if N number of people are allowed to watch the play. If anybody leaves
the Theater during the play then other person can be allowed to watch the play.
• It is called CountDownLatch if no one is allowed to enter until every person vacates the theater.
Here each person has free will to leave the theater.
• It is called CyclicBarrier if the play will not start until every person enters the theater. Here a
showman can not start the show until all the persons enter and grab the seat. Once the play is
finished the same barrier will be applied for next show.
• Here, a person is a thread, a play is a resource.

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<>();

// Crunch some numbers and store the partial result


for (int i = 0; i < NUM_PARTIAL_RESULTS; i++) {
Integer num = random.nextInt(10);
System.out.println(thisThreadName
+ ": Crunching some numbers! Final result
- " + num);
partialResult.add(num);
}

partialResults.add(partialResult);
try {
System.out.println(thisThreadName
+ " waiting for others to reach barrier.");
cyclicBarrier.await();
} catch (InterruptedException e) {
// ...
} catch (BrokenBarrierException e) {
// ...
}
}
}

public class CyclicBarrierDemo {

// Previous code

public void runSimulation(int numWorkers, int numberOfPartialResults) {


NUM_PARTIAL_RESULTS = numberOfPartialResults;
NUM_WORKERS = numWorkers;

cyclicBarrier = new CyclicBarrier(NUM_WORKERS, new AggregatorThread());

System.out.println("Spawning " + NUM_WORKERS


+ " worker threads to compute "
+ NUM_PARTIAL_RESULTS + " partial results each");

for (int i = 0; i < NUM_WORKERS; i++) {


Thread worker = new Thread(new NumberCruncherThread());
worker.setName("Thread " + i);
worker.start();
}
}

public static void main(String[] args) {


CyclicBarrierDemo demo = new CyclicBarrierDemo();
demo.runSimulation(5, 3);
}
}

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 memory location on which to operate (M)


• The existing expected value (A) of the variable
• The new value (B) which needs to be set
• The CAS operation updates atomically the value in M to B, but only if the existing value in M
matches A, otherwise no action is taken.

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

AtomicXXX is equivalent of volatile + synchronized even though the implementation is different.


AtomicXXX extends volatile variables + compareAndSet methods but does not use synchronization.

There are various methods present in Atomic Variables


get(): This method is used to fetch the current value of the Atomic Variable in Java.
addAndGet(int val): This method is used to atomically add the given value to the current value of the
Atomic Variable in Java.
decrementAndGet(): This method is used to decrease the value of the variable.

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);
}
}
}

public class usingAtomic {


public static void main(String args[])throws InterruptedException{
threadCount count = new threadCount();
Thread first = new Thread(count, "First");
Thread second = new Thread(count, "Second");
first.start();
second.start();
first.join();
second.join();
System.out. println("Loop count using Atomic Variables in
Java: "+count.total);
}
}

Thread Pool Executor:


Executors class provide simple implementation of ExecutorService using ThreadPoolExecutor but
ThreadPoolExecutor provides much more feature than that. We can specify the number of threads that
will be alive when we create ThreadPoolExecutor instance and we can limit the size of thread pool and
create our own RejectedExecutionHandler implementation to handle the jobs that can’t fit in the
worker queue. Here is our custom implementation of RejectedExecutionHandler interface.
• Group of threads readily available
• CPU intensive: Thread pool size=no of cores
• I/O task: Thread pool size>no of cores
• No need to manually create, start and join threads

public class MyMonitorThread implements Runnable


{
private ThreadPoolExecutor executor;
private int seconds;
private boolean run=true;

public MyMonitorThread(ThreadPoolExecutor executor, int delay)


{
this.executor = executor;
this.seconds=delay;
}
public void shutdown(){
this.run=false;
}
@Override
public void run()
{
while(run){
System.out.println(
String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s,
isTerminated: %s",
this.executor.getPoolSize(),
this.executor.getCorePoolSize(),
this.executor.getActiveCount(),
this.executor.getCompletedTaskCount(),
this.executor.getTaskCount(),
this.executor.isShutdown(),
this.executor.isTerminated()));
try {
Thread.sleep(seconds*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
}

public class WorkerPool {


public static void main(String args[]) throws InterruptedException{
//RejectedExecutionHandler implementation
RejectedExecutionHandlerImpl rejectionHandler = new RejectedExecutionHandlerImpl();
//Get the ThreadFactory implementation to use
ThreadFactory threadFactory = Executors.defaultThreadFactory();
//creating the ThreadPoolExecutor
ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new
ArrayBlockingQueue<Runnable>(2), threadFactory, rejectionHandler);
//start the monitoring thread
MyMonitorThread monitor = new MyMonitorThread(executorPool, 3);
Thread monitorThread = new Thread(monitor);
monitorThread.start();
//submit work to the thread pool
for(int i=0; i<10; i++){
executorPool.execute(new WorkerThread("cmd"+i));
}

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

ExecutorService execService = Executors.newFixedThreadPool(5);


execService.execute(new Runnable() {
public void run() {
System.out.println(“asynchronous task created”);
}
});
execService.shutdown();

• The framework manages a homogeneous pool of worker threads.


• Thread pools will be tightly coupled with the work queue.
• The lifecycle of worker thread is as follows:
• Threads in the thread pool requests for new tasks from the work queue
• Executes the task provided by the work queue.
• Goes back to the thread pool for future tasks.
• Using thread pools over thread-per-task comes with lot of benefits as follows below:
• Reusing an already created thread which is waiting for the new task in the thread pool reduces
the costs of creation of a new thread for every new request, it will help in improving
responsiveness.
• The size of the thread pool plays a major role in keeping your processors busy and making
efficient usage of the resources, while not having many threads that your application runs out of
memory or pressurize the system while competing among threads for resources.

ExecutorService executor = Executors.newCachedThreadPool();


List<Callable<Integer>> listOfCallable = Arrays.asList(
() -> 1,
() -> 2,
() -> 3);
try {
List<Future<Integer>> futures = executor.invokeAll(listOfCallable);
int sum = futures.stream().map(f -> {
try {
return f.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}).mapToInt(Integer::intValue).sum();
System.out.println(sum);
} catch (InterruptedException e) {// thread was interrupted
e.printStackTrace();
} finally {
// shut down the executor manually
executor.shutdown();
}
}
}

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

Shutting down the ExecutorService


ExecutorService provides two methods for shutting down an executor -
shutdown() - when shutdown() method is called on an executor service, it stops accepting new tasks,
waits for previously submitted tasks to execute, and then terminates the executor.
shutdownNow() - this method interrupts the running task and shuts down the executor immediately.
Executor and ExecutorService are the main Interfaces. ExecutorService can execute Runnable and
Callable tasks.
Executor interface has execute method. ExecutorService has submit(), invokeAny() and invokeAll().
Executors utility/Factory class that has Methods that create and return an ExecutorService.
public class ExecutorServiceTest {
public static void main(String[] args) {
ExecutorService s=Executors.newFixedThreadPool(10);
s.submit(new Task("Mythread1"));
s.submit(new Task("Mythread2"));
//s.shutdown();
}
}

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);

Problems with ExecutorService:


Keeping an unused ExecutorService alive:
Wrong thread-pool capacity while using fixed length thread pool:
Calling a Future‘s get() method after task cancellation:
Unexpectedly long blocking with Future‘s get() method:

Limitations of the Future


• A Future cannot be mutually complete.
• We cannot perform further action on a Future's result without blocking.
• Future has not any exception handling.
• We cannot combine multiple futures.

Execute and Submit method difference:


As you see from the JavaDoc execute(Runnable) does not return anything.

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

Future<?> future = executor.submit(longRunningJob);


...
//long running job is taking too long
future.cancel(true);

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.

RecursiveTask: return something


In this example, we use an array stored in the arr field of the CustomRecursiveTask class to represent the
work. The createSubtasks() method recursively divides the task into smaller pieces of work until each
piece is smaller than the threshold. Then the invokeAll() method submits the subtasks to the common
pool and returns a list of Future.
To trigger execution, the join() method is called for each subtask.
public class CustomRecursiveTask extends RecursiveTask<Integer> {
private int[] arr;

private static final int THRESHOLD = 20;

public CustomRecursiveTask(int[] arr) {


this.arr = arr;
}

@Override
protected Integer compute() {
if (arr.length > THRESHOLD) {
return ForkJoinTask.invokeAll(createSubtasks())
.stream()
.mapToInt(ForkJoinTask::join)
.sum();
} else {
return processing(arr);
}
}

private Collection<CustomRecursiveTask> createSubtasks() {


List<CustomRecursiveTask> dividedTasks = new ArrayList<>();
dividedTasks.add(new CustomRecursiveTask(
Arrays.copyOfRange(arr, 0, arr.length / 2)));
dividedTasks.add(new CustomRecursiveTask(
Arrays.copyOfRange(arr, arr.length / 2, arr.length)));
return dividedTasks;
}

private Integer processing(int[] arr) {


return Arrays.stream(arr)
.filter(a -> a > 10 && a < 27)
.map(a -> a * 10)
.sum();
}
}

RecursiveAction: will not return anything


the example splits the task if workload.length() is larger than a specified threshold using
the createSubtask() method.
The String is recursively divided into substrings, creating CustomRecursiveTask instances that are based
on these substrings.
As a result, the method returns a List<CustomRecursiveAction>.
The list is submitted to the ForkJoinPool using the invokeAll() method:

public class CustomRecursiveAction extends RecursiveAction {

private String workload = "";


private static final int THRESHOLD = 4;

private static Logger logger =


Logger.getAnonymousLogger();

public CustomRecursiveAction(String workload) {


this.workload = workload;
}

@Override
protected void compute() {
if (workload.length() > THRESHOLD) {
ForkJoinTask.invokeAll(createSubtasks());
} else {
processing(workload);
}
}

private List<CustomRecursiveAction> createSubtasks() {


List<CustomRecursiveAction> subtasks = new ArrayList<>();

String partOne = workload.substring(0, workload.length() / 2);


String partTwo = workload.substring(workload.length() / 2, workload.length());

subtasks.add(new CustomRecursiveAction(partOne));
subtasks.add(new CustomRecursiveAction(partTwo));

return subtasks;
}

private void processing(String work) {


String result = work.toUpperCase();
logger.info("This result - (" + result + ") - was processed by "
+ Thread.currentThread().getName());
}
}

Find the max number using Fork Join pool:


class SequentialMaxFinding {

// linear search O(N)


public long max(long[] nums) {

long max = nums[0];

for(int i=1; i<nums.length;++i)


if(nums[i] > max)
max = nums[i];

return max;
}
}

class ParallelTask extends RecursiveTask<Long> {

private long[] nums;


private int lowIndex;
private int highIndex;

public ParallelTask(long[] nums, int lowIndex, int highIndex) {


this.nums = nums;
this.lowIndex = lowIndex;
this.highIndex = highIndex;
}
@Override
protected Long compute() {

// if the array is small - then we use sequential approach


if(highIndex - lowIndex < 5000) {
return sequentialMaxFinding();
} else {
// we have to use parallelization
int middleIndex = (highIndex+lowIndex)/2;

ParallelTask task1 = new ParallelTask(nums, lowIndex,


middleIndex);
ParallelTask task2 = new ParallelTask(nums, middleIndex+1,
highIndex);

invokeAll(task1, task2);

return Math.max(task1.join(), task2.join());


}
}

private Long sequentialMaxFinding() {

long max = nums[lowIndex];

for(int i=lowIndex+1;i<highIndex;++i)
if(nums[i] > max)
max = nums[i];

return max;
}
}

class ParallelMaxTask{

public static void main(String[] args) {

long[] nums = createNumbers(500000000);

ForkJoinPool pool = new


ForkJoinPool(Runtime.getRuntime().availableProcessors());
SequentialMaxFinding sequential = new SequentialMaxFinding();

long start = System.currentTimeMillis();


System.out.println("Max: " + sequential.max(nums));
System.out.println("Time: " + (System.currentTimeMillis() - start));

ParallelTask parallel = new ParallelTask(nums, 0, nums.length);


start = System.currentTimeMillis();
System.out.println("Max: " + pool.invoke(parallel));
System.out.println("Time: " + (System.currentTimeMillis() - start));
}

private static long[] createNumbers(int n) {

Random random = new Random();

long[] nums = new long[n];

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

ExecutorService executor = Executors.newFixedThreadPool(5);

Future<String> result = executor.submit(new FutureTask());

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);

Limitations of the Future


• A Future cannot be mutually complete.
• We cannot perform further action on a Future's result without blocking.
• Future has not any exception handling.
• We cannot combine multiple futures.

CompletableFuture: asynchronous and non-blocking programming, because there is no get() methods


that block the main thread but there are callbacks implemented instead.

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.

Running asynchronous computation using runAsync():


If you want to run some background task asynchronously and don’t want to return anything from the
task, then you can use CompletableFuture.runAsync() method. It takes a Runnable object and returns
CompletableFuture<Void>.

// Run a task specified by a Runnable Object asynchronously.


CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
// Simulate a long-running Job
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
System.out.println("I'll run in a separate thread than the main thread.");
}
});

// Block and wait for the future to complete


future.get()

Run a task asynchronously and return the result using supplyAsync():


CompletableFuture.runAsync() is useful for tasks that don’t return anything. But what if you want to
return some result from your background task?
Well, CompletableFuture.supplyAsync() is your companion. It takes a Supplier<T> and
returns CompletableFuture<T> where T is the type of the value obtained by calling the given supplier –

A Supplier<T> is a simple functional interface which represents a supplier of results. It has a


single get() method where you can write your background task and return the result.
// Run a task specified by a Supplier object asynchronously
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Result of the asynchronous computation";
}
});

// Block and get the result of the Future


String result = future.get();
System.out.println(result);

1. static Supplier<String> sup=()->"Hello";


public static void main(String[] args) {
CompletableFutureTest cft= new CompletableFutureTest(); //supplier
CompletableFuture.supplyAsync(sup).thenApply((s)->s.toUpperCase()).//function
thenAccept((s1)->System.out.println(s1));//consumer
}

2.CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));

3.CompletableFuture<OrchestrationContext> completableFuture = CompletableFuture.supplyAsync(() ->


{
return prepareWorkflow(initialContext);
}).thenApply(stage1Context -> {
return performObjectCreation(stage1Context);

}).handle((finalContext, ex) -> {


return performFinalResponse(finalContext,
initialContext.getOrchRequest().getProductid(), ex);
});

completableFuture.get();

Transforming and acting on a CompletableFuture:


The CompletableFuture.get() method is blocking. It waits until the Future is completed and returns the
result after its completion.
But, that’s not what we want right? For building asynchronous systems we should be able to attach a
callback to the CompletableFuture which should automatically get called when the Future completes.
That way, we won’t need to wait for the result, and we can write the logic that needs to be executed
after the completion of the Future inside our callback function.
You can attach a callback to the CompletableFuture
using thenApply(), thenAccept() and thenRun() methods -
thenApply: You can use thenApply() method to process and transform the result of a
CompletableFuture when it arrives. It takes a Function<T,R> as an argument. Function<T,R> is a simple
functional interface representing a function that accepts an argument of type T and produces a result of
type R –
// Create a CompletableFuture
CompletableFuture<String> whatsYourNameFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return "Fayaz";
});

// Attach a callback to the Future using thenApply()


CompletableFuture<String> greetingFuture =
whatsYourNameFuture.thenApply(name -> {
return "Hello " + name;
});

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");

thenAccept() and thenRun():


If you don’t want to return anything from your callback function and just want to run some piece of
code after the completion of the Future, then you can use thenAccept() and thenRun() methods. These
methods are consumers and are often used as the last callback in the callback chain.

CompletableFuture.thenAccept() takes a Consumer<T> and returns CompletableFuture<Void>. It has


access to the result of the CompletableFuture on which it is attached.
CompletableFuture.supplyAsync(() -> {
return ProductService.getProductDetail(productId);
}).thenAccept(product -> {
System.out.println("Got product detail from remote service " + product.getName())
});

While thenAccept() has access to the result of the CompletableFuture on which it is


attached, thenRun() doesn’t even have access to the Future’s result. It takes a Runnable and
returns CompletableFuture<Void> -

Combine two dependent futures using thenCompose():


Let’s say that you want to fetch the details of a user from a remote API service and once the user’s detail
is available, you want to fetch his Credit rating from another service.
CompletableFuture<User> getUsersDetail(String userId) {
return CompletableFuture.supplyAsync(() -> {
return UserService.getUserDetails(userId);
});
}

CompletableFuture<Double> getCreditRating(User user) {


return CompletableFuture.supplyAsync(() -> {
return CreditRatingService.getCreditRating(user);
});
}
CompletableFuture<CompletableFuture<Double>> result = getUserDetail(userId)
.thenApply(user -> getCreditRating(user));

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));

Combine two independent futures using thenCombine()


While thenCompose() is used to combine two Futures where one future is dependent on the
other, thenCombine() is used when you want two Futures to run independently and do something after
both are complete.
System.out.println("Retrieving weight.");
CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 65.0;
});

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);
});

System.out.println("Your BMI is - " + combinedFuture.get());

Running Multiple Futures in Parallel


When we need to execute multiple Futures in parallel, we usually want to wait for all of them to execute
and then process their combined results.
The CompletableFuture.allOf static method allows to wait for the completion of all of the Futures
provided as a var-arg:

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!");

assertEquals("Hello, Stranger!", completableFuture.get());

CompletableFuture with ExecutorService

ExecutorService cpuExecutor = Executors.newFixedThreadPool(5);


ExecutorService ioExecutor = Executors.newCachedThreadPool();

CompletableFuture.supplyAsync(() -> "Hello Fayaz!", cpuExecutor)


.thenApplyAsync(s -> s.toUpperCase(),
ioExecutor)
.thenApply(s -> s + " Welcome")

.thenAccept(System.out::println);//HELLO FAYAZ! Welcome

Solving Philosopher problem using Multithreading with Locks.


We can use same for the Student Library simulation

#State.java
public enum State {
LEFT, RIGHT;
}

#Constants.java
class Constants {

private Constants(){
}

public static final int NUMBER_OF_PHILOSOPHERS = 5;


public static final int NUMBER_OF_CHOPSTICKS = NUMBER_OF_PHILOSOPHERS;
public static final int SIMULATION_RUNNING_TIME = 5*1000;

//ChopStick.java
package threads.PhilosopherProblem;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ChopStick {

private Lock lock;


private int id;

public ChopStick(int id){


this.id = id;
this.lock = new ReentrantLock();
}

public boolean pickUp(Philosopher philosopher, State state) throws InterruptedException{

if( this.lock.tryLock(10, TimeUnit.MILLISECONDS)){


System.out.println(philosopher+" picked up "+state.toString()+" "+this);
return true;
}

return false;
}

public void putDown(Philosopher philosopher, State state) {


this.lock.unlock();
System.out.println(philosopher+" put down "+this);
}

@Override
public String toString() {
return "Chopstick-"+this.id;
}
}

//Philosopher.java
class Philosopher implements Runnable {

private int id;


private ChopStick leftChopStick;
private ChopStick rightChopStick;
private volatile boolean isFull = false;
private Random random;
private int eatingCounter;

public Philosopher(int id, ChopStick leftChopStick, ChopStick rightChopStick){


this.id = id;
this.leftChopStick = leftChopStick;
this.rightChopStick = rightChopStick;
this.random = new Random();
}

@Override
public void run() {

try{

while( !isFull ){

think();

if( leftChopStick.pickUp(this, State.LEFT) ){


if( rightChopStick.pickUp(this, State.RIGHT)){
eat();
rightChopStick.putDown(this,
State.RIGHT);
}

leftChopStick.putDown(this, State.LEFT);
}
}
}catch(Exception e){
e.printStackTrace();
}
}

private void think() throws InterruptedException {


System.out.println(this+" is thinking...");
Thread.sleep(this.random.nextInt(1000));
}

private void eat() throws InterruptedException {


System.out.println(this+" is eating...");
this.eatingCounter++;
Thread.sleep(this.random.nextInt(1000));
}

public int getEatingCounter(){


return this.eatingCounter;
}

public void setFull(boolean isFull){


this.isFull = isFull;
}

@Override
public String toString() {
return "Philosopher-"+this.id;
}
}

//Main class
public class App {

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

ExecutorService executorService = null;


Philosopher[] philosophers = null;

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);

for(Philosopher philosopher : philosophers){


philosopher.setFull(true);
}
}finally{

executorService.shutdown();

while(!executorService.isTerminated()){
Thread.sleep(1000);
}

for(Philosopher philosopher : philosophers ){


System.out.println(philosopher+" eat
#"+philosopher.getEatingCounter());
}

}
}

ParalellStreams:
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
public class ParalellTest {

public static void main(String[] args) {


// TODO Auto-generated method stub
long start=System.currentTimeMillis();
long count=IntStream.rangeClosed(2,
Integer.MAX_VALUE/100).parallel().filter(ParalellTest::isPrime).count();
System.out.println(count +" Time taken: "+ (start-
System.currentTimeMillis()));

public static boolean isPrime(long num) {

boolean isPrime=false;
//if(num==1)isPrime= false;
if(num==2)isPrime= true;
//if(num%2==0)isPrime= false;

long maxDivisor=(long) Math.sqrt(num);

for (int i=3;i<maxDivisor;i+=2) {


if(num%i==0) {
isPrime= false;
}else {
isPrime= true;
}
}
return isPrime;
}
}

public class SumUsingParalellStreams {

public static void main(String[] args) {

// parallel() - because we have to make sure that the given


// stream can be parallelized
// under the hood the fork-join framework is used
long start = System.currentTimeMillis();
System.out.println(sum(2000000000));
System.out.println("Time taken sequential: " + (System.currentTimeMillis()
- start));

start = System.currentTimeMillis();
System.out.println(parallelSum(2000000000));
System.out.println("Time taken parallel: " + (System.currentTimeMillis() -
start));
}

private static long sum(long n) {


return LongStream.rangeClosed(1, n).reduce(0L, Long::sum);
}

private static long parallelSum(long n) {


return LongStream.rangeClosed(1, n).parallel().reduce(0L, Long::sum);
}

//Time taken sequential: 3830


//Time taken parallel: 116

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.)

// finish within 1 second


try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}

public class RestQuery {

public static String run() {


System.out.println("REST operation started...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("REST operation finished...");
return " 23";
}
}

public class DbQuery {

public static String run() {


System.out.println("DB operation started...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("DB operation finished...");
return "Adam";
}
}

try(var service = Executors.newVirtualThreadPerTaskExecutor()) {

String res = CompletableFuture


.supplyAsync(DbQuery::run, service)
.thenCombine(CompletableFuture
.supplyAsync(RestQuery::run, service),
(res1, res2) -> {
return res1
+ res2;
})
.join();

System.out.println(res);

// try with resources closes (shuts down the threads)


automatically
// the threads will be executed (we will wait for them to finish)
}

Virtual Threads pinning:

public class VirtualTask {

public static void run() {


System.out.println("Started ... " + Thread.currentThread().getName());

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

System.out.println("Finish ... " + Thread.currentThread().getName());


}
}
public class VirtualThreadsTest {

public static void main(String[] args) {


// TODO Auto-generated method stub

try(var service = Executors.newVirtualThreadPerTaskExecutor()) {


service.submit(VirtualTask::run);
service.submit(VirtualTask::run);
service.submit(VirtualTask::run);

// wait
}

Structured Concurrency:
public class LongProcessFail implements Callable<String> {

private int timeToSleep;


private String result;
private boolean fail;

public LongProcessFail(int timeToSleep, String result, boolean fail) {


this.timeToSleep = timeToSleep;
this.fail = fail;
this.result = result;
}

@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");
}

System.out.println("Finish thread: " + result);


return result;
}

import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

public class StructuredConcurrencyOne {

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

// 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>()) {

var process1 = new LongProcess(1, "result 1");


var process2 = new LongProcess(7, "result 2");

// we submit the tasks in parallel


Subtask<String> res1 = scope.fork(process1);
Subtask<String> res2 = scope.fork(process2);

// BECAUSE VIRTUAL THREADS !!!


// join() method wait for all threads to complete
scope.join();
// combine the results
// get() will not block because the join() waits for the threads
to finish

if(res1.state() == State.SUCCESS)
System.out.println(res1.get());

if(res2.state() == State.SUCCESS)
System.out.println(res2.get());

// it will shutdown the scope after all child threads terminate


}

public class StructuredConcurrencySuccess {

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

// 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>()) {

var process1 = new LongProcessFail(1, "result 1", true);


var process2 = new LongProcessFail(5, "result 2", true);

// we submit the tasks in parallel


Subtask<String> res1 = scope.fork(process1);
Subtask<String> res2 = scope.fork(process2);

// BECAUSE VIRTUAL THREADS !!!


scope.join();

String result = null;

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/

You might also like