1.    Thread Pool Executor: A thread pool is a group of worker threads that can be reused for executing multiple tasks. The ThreadPoolExecutor class is the most commonly used executor in Java. It allows you to submit tasks for execution and manages the lifecycle of the worker threads. You can configure the size of the thread pool, keep track of completed tasks, and handle rejected tasks.

2.    ForkJoinPoolExecutor: This executor is designed for performing fork-join operations. It breaks down a task into smaller subtasks and then combines their results. It is useful when you have a recursive algorithm that can be parallelized.

3.    RejectedExecutionHandler: This handler is used to handle tasks that cannot be executed due to a lack of resources, such as insufficient memory or too many pending tasks.

 



 

 

 

 

As with deadlock, livelocked threads are unable to make further progress.

However, the threads are not blocked — they are simply too busy responding to each other to resume work

Deadlock: situation where nobody progress, doing nothing (sleeping, waiting etc..). CPU usage will be low;

 

Livelock: situation where nobody progress, but CPU is spent on the lock mechanism and not on your calculation;

 

Starvation: situation where one procress never gets the chance to run;

by pure bad luck or by some of its property (low priority, for example);

 

Spinlock: technique of avoiding the cost waiting the lock to be freed.

 

Java-Example for a deadlock:

Thread A : waits for lock 1

Thread B : waits for lock 2

Thread A : holds lock 1

Thread B : holds lock 2

Thread B : waits for lock 1

Thread A : waits for lock 2

 

Java-Example for a livelock:

Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...

 

 

 

 

 

 

 

 

 

 

https://slideplayer.com/slide/3281719/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1.      SimpleAsyncTaskExecutor:

·        This is a basic implementation of the TaskExecutor interface.

·        It creates a new thread for each task, making it suitable for short-lived asynchronous tasks.

·        Not recommended for heavy workloads due to the overhead of creating new threads.

 

@Bean

public AsyncTaskExecutor asyncTaskExecutor() {

return new SimpleAsyncTaskExecutor(); }

 

2.      ThreadPoolTaskExecutor:

·        This implementation allows you to configure a pool of worker threads.

·        It's more efficient than SimpleAsyncTaskExecutor for handling a large number of tasks.

·        You can configure properties like the core pool size, max pool size, and queue capacity.

 

@Bean

public ThreadPoolTaskExecutor taskExecutor() {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

executor.setCorePoolSize(5);

executor.setMaxPoolSize(10);

executor.setQueueCapacity(25);

return executor; }

 

3.      CommonsRequestLoggingFilter:

·        This is a filter that logs details of each HTTP request.

·        It's not a thread pool per se, but it can be configured to use a thread pool for logging purposes, especially when logging requests can be time-consuming.

 

@Bean

public CommonsRequestLoggingFilter requestLoggingFilter() {

CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();

filter.setTaskExecutor(new SimpleAsyncTaskExecutor());

return filter; }

 

4.      WorkManagerTaskExecutor:

·        This is an implementation that adapts a Java EE WorkManager to the Spring TaskExecutor interface.

 

@Bean

public WorkManagerTaskExecutor workManagerTaskExecutor()

 { return new WorkManagerTaskExecutor(); }

 

These are just a few examples, and Spring Boot provides flexibility for configuring and customizing thread pools based on your application's requirements. Depending on your use case, you might choose a different implementation or configure properties like core pool size, max pool size, and queue capacity accordingly

 

FixedThreadPool:

·        The FixedThreadPool creates a fixed number of threads and reuses them to execute tasks.

·        It's suitable when you want to limit the number of concurrent tasks.

·         

ExecutorService executor = Executors.newFixedThreadPool(5);

 

2.      CachedThreadPool:

·        The CachedThreadPool creates new threads as needed and reuses existing ones.

·        It's suitable when the number of tasks can vary, and you want to reuse threads to avoid the overhead of thread creation.

·         

ExecutorService executor = Executors.newCachedThreadPool();

 

3.      ScheduledThreadPool:

·        The ScheduledThreadPool is designed for scheduling tasks to run after a certain delay or periodically.

·        It's suitable for scenarios where tasks need to be executed at fixed intervals or with a delay.

·         

ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);

 

4.      SingleThreadExecutor:

·        The SingleThreadExecutor creates a single worker thread to execute tasks sequentially.

·        It's useful when tasks must be executed in a specific order or when you want to ensure that only one task is executed at a time.

·         

ExecutorService executor = Executors.newSingleThreadExecutor();

 

These thread pool implementations are part of the Executors utility class in the java.util.concurrent package. While these are convenient for many use cases, in a Spring Boot application, you might prefer to use the ThreadPoolTaskExecutor bean for more fine-grained control and integration with the Spring ecosystem, as mentioned in the previous response. The ThreadPoolTaskExecutor allows you to set properties such as core pool size, max pool size, and queue capacity to tailor the thread pool to your application's specific needs.

 

 

1.      Visibility:

·        The volatile keyword ensures that any thread reading a volatile variable sees the most recent modification made by any other thread.

·        Without volatile, changes made by one thread may not be visible to other threads.

2.      Atomicity:

·        The volatile keyword guarantees atomic reads and writes for the variable. However, it does not provide atomicity for compound actions (e.g., check-then-act).

3.      No Caching:

·        volatile variables are not cached in thread-local memory. When a thread reads a volatile variable, it always reads the variable's current value from main memory.

 

 

Atomic Variables

 

that support atomic operations (read-modify-write operations) without the need for explicit synchronization.

 

 

package com.govtech.demo.console;

/**
 *
@author Sarav on 12 Mar 2024
 *
@project govtech
 *
@package com.govtech.demo.console
 *
@class GFG
 */
// Java Program demonstrating Introduction to Java Executor
// Framework

// Importing concurrent classes from java.util package
import java.util.concurrent.*;

// Class 1
// Helper Class implementing runnable interface Callable
class Task implements Callable<String> {
   
// Member variable of this class
   
private String message;

   
// Constructor of this class
   
public Task(String message)
    {
       
// This keyword refers to current instance itself
       
this.message = message;
    }

   
// Method of this Class
   
public String call() throws Exception
   
{
       
return "Hiiii " + message + "!";
    }
}
// Class 2
// Main Class
// ExecutorExample
public class GFG {

   
// Main driver method
   
public static void main(String[] args)
    {

       
// Creating an object of above class
        // in the main() method
       
Task task = new Task("GeeksForGeeks");

       
// Creating object of ExecutorService class and
        // Future object Class
       
ExecutorService executorService
               
= Executors.newFixedThreadPool(4);
       
Future<String> result
               
= executorService.submit(task);

       
// Try block to check for exceptions
       
try {
           
System.out.println(result.get());
        }

       
// Catch block to handle the exception
       
catch (InterruptedException
              
| ExecutionException e) {

           
// Display message only
           
System.out.println(
                   
"Error occurred while executing the submitted task");

           
// Print the line number where exception occurred
           
e.printStackTrace();
        }

       
// Cleaning resource and shutting down JVM by
        // saving JVM state using shutdown() method
       
executorService.shutdown();
    }
}

 

Blocking vs. Non-Blocking:

·        Future: Primarily relies on blocking operations. You use the get() method to retrieve the result, which blocks the current thread until the operation finishes.

·        CompletableFuture: Encourages non-blocking programming. It provides methods like thenApply, thenAccept, and thenRun to process the result asynchronously, allowing your application to remain responsive.

 

 

Differences between Future and CompletableFuture


class FutureExample {
   
public static void main(String[] args) throws InterruptedException, ExecutionException {
       
ExecutorService executor = Executors.newSingleThreadExecutor();

       
// Submit a task and get a Future
       
Future<String> future = executor.submit(() -> {
           
Thread.sleep(2000);
           
return "Task completed";
        });

       
// Do other work while the task is running asynchronously

        // Block and wait for the result (this may block the main thread)
       
String result = future.get();
       
System.out.println(result);

       
executor.shutdown();
    }
}

class CompletableFutureExample {
   
public static void main(String[] args) throws InterruptedException, ExecutionException {
       
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
           
try {
               
Thread.sleep(2000);
            }
catch (InterruptedException e) {
               
e.printStackTrace();
            }
           
return "Task completed";
        });

       
// Do other work while the task is running asynchronously

        // Attach a callback to handle the result when it's ready
       
future.thenAccept(result -> System.out.println(result));

       
// Alternatively, block and wait for the result
        // String result = future.get();
        // System.out.println(result);

        // Allow some time for the asynchronous task to complete
       
Thread.sleep(3000);
    }
}

 

 

 

 

 

wait() and sleep() are both important methods for managing concurrency, but they serve distinct purposes. wait() is used for inter-thread communication and synchronization, while sleep() is used to introduce pauses or delays in thread execution.

 

Thread Safety : that allows it to be used safely by Multiple Threads without causing any unexpected behavior or data corruption

Issues with Shared Resources: Race condition, Deadlock and data Inconsistency

How to make ThreadSafe

Immutable Objects

Synchronize shared Resource Access

In a chess game, continuous repetition will end up match draw,