Java Multithreading (Concurrency) Interview Questions Java Multithreading (Concurrency) Interview Questions

Page content

Comprehensive List of Java Multithreading (Concurrency) Interview Questions based on my personal interview experience over the last few years. Keep following this post link for regular updates.

Also Read Core Java Interview Questions
Also Read Java 8 Interview Questions

We can signal two threads to run alternatively using different ways in Java:

Using wait notify
  1. 1st thread print “Ping” and go to wait state.
  2. 2nd thread wakes up from wait state, print “Ping”, notify 1st thread, goes back to wait state.
  3. 1st thread wakes up from wait state, print “Pong”, notify 2nd thread, goes back to wait state.
  4. Step 2 and 3 repeats and print “Ping Pong” alternatively.
Using ReentrantLock Condition

ReentrantLock Condition provides two methods await() and signal() which works very similar to wait notify methods.

Read Ping Pong using Threads in Java for Java programs using Object’s wait-notify and ReentrantLock Condition’s await-signal.

How to run 5 threads sequentially?

Smart Answer: The whole point of having threads is to run them concurrently. If you want to run threads sequentially, better not use them.

Using Thread.join()

Let’s create a task which prints 1 to 100

class Task implements Runnable{
	int index;
	Task(int index){
		this.index = index;
	}
	@Override
	public void run() {		
		for(int i = 1; i <= 100; i++) {
			System.out.print("Thread" + index + ": " + i + " ");
		}
		System.out.println();
	}	
}

Let’s create 5 threads to run the tasks sequentially by joining the them one after the another.

Thread t1 = new Thread(new Task(1));
Thread t2 = new Thread(new Task(2));
Thread t3 = new Thread(new Task(3));
Thread t4 = new Thread(new Task(4));
Thread t5 = new Thread(new Task(5));

t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
t4.start();
t4.join();
t5.start();
Using SingleThreadExecutor

You can create single thread executor service and submit the task 5 times to execute them sequentially.

ExecutorService service = Executors.newSingleThreadExecutor();
for(int i=1; i<= 5; i++){
  Task task = new Task(i);
  service.submit(task);
}

A Task is running in a separate thread. Stop the task if it exceeds 10 minutes.

We can use Thread.sleep() to sleep the thread for 10 minutes and Thread.interrupt() to interrupt a thread after 10 minutes.

We can interrupt a thread in three ways:-

  1. Using thread interrupt()
  2. Using Executor thread pool shutdownNow()
  3. Using Callable returned future cancel()
public class StopThreadAfterTimeout {
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		usingThreadInterrupt();
		//usingThreadPoolShutdownNow();
		//usingFutureCancel();
	}

	private static void usingThreadInterrupt() throws InterruptedException {
		// 1. create a thread
		Thread t1 = new Thread(() -> {
			while (!Thread.currentThread().isInterrupted()) {
				// next step
			}
		});

		// 2. timeout after 10 minutes
		Thread.sleep(10 * 60 * 1000);

		// 3. stop the thread
		t1.interrupt();
	}

	private static void usingThreadPoolShutdownNow() throws InterruptedException {
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		// 1. create a thread
		threadPool.submit(() -> {
			while (!Thread.currentThread().isInterrupted()) {
				// next step
			}
		});

		// 2. timeout after 10 minutes
		Thread.sleep(10 * 60 * 1000);

		// 3. stop the thread
		threadPool.shutdownNow(); // internally call thread interrupt
	}

	private static void usingFutureCancel() throws InterruptedException, ExecutionException {
		ExecutorService service = Executors.newFixedThreadPool(2);

		// 1. submit new callable task which return future object
		Future<?> future = service.submit(() -> {
			while (!Thread.currentThread().isInterrupted()) {
				// next step
			}
		});

		// 2. wait for 10 minutes to get response
		try {
			future.get(10, TimeUnit.MINUTES);
		} catch (TimeoutException e) {
			
			// 3. stop the thread
			future.cancel(true); // internally call thread interrupt
		}
	}
}

How to decide Thread Pool Size?

It depends on the type of tasks you want to execute:-

1. CPU Intensive Tasks

If you are executing CPU intensive tasks such as cryptographic hash function then max thread pool size should be equal to number of cores in processor.
For example a 4 core processor can run only 4 threads at a time so threadPoolSize = 4 is ideal for threads taking lot of CPU.

public class IdealThreadPoolSize {

	public static void main(String[] args) {
		// get count of available cores
		int coreCount = Runtime.getRuntime().availableProcessors();
		ExecutorService service = Executors.newFixedThreadPool(coreCount);

		// submit the tasks for execution
		for (int i = 0; i < 100; i++) {
			service.execute(new CpuIntestiveTask());
		}
	}

	static class CpuIntestiveTask implements Runnable {
		public void run() {
			// some CPU intensive operations
		}
	}
}

2. IO Intensive Tasks

If you are executing IO intensive tasks such as Database or HTTP calls, where threads have a tendency to wait after requesting for the resources. In such case max thread pool size should be larger than the number of cores in processor.
For example for a threadPoolSize = 20, if 16 threads are waiting for resources or blocked due to IO operations, other 4 threads can be processed concurrently by 4 core processor.
Ideal number of threadPoolSize depends on rate of task submission and average task wait time. ThreadPool typically place all the submitted task in BlockingQueue which is thread-safe. Idle threads fetch the task from the queue and process them.

public class IdealThreadPoolSize {

	public static void main(String[] args) {
		// much higher count for IO tasks
		ExecutorService service = Executors.newFixedThreadPool(20);

		// submit the tasks for execution
		for (int i = 0; i < 100; i++) {
			service.execute(new IOTask());
		}
	}

	static class IOTask implements Runnable {
		public void run() {
			// some IO operations which will cause thread to block/wait
		}
	}
}

What are the types of Executor Thread Pool?

  1. FixedThreadPool - Create a fixed number of threads in the beginning and place them in the pool.
  2. SingleThreadExecutor - Create a single thread and place it in the pool.
  3. CachedThreadPool - Create a new thread only if all the other threads are busy and place it in the pool. Ideal for short lived tasks.
  4. ScheduledThreadPool - Create a fixed number of threads in the beginning and place them in the pool. Create a new thread if all the other threads are busy. Ideal for running the tasks at regular time interval or delay.
// for fixed number of threads
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

// for a pool of single thread
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

// for lot of short lived tasks
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

// for scheduling of tasks
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);

// task to run after 10 second delay
scheduledThreadPool.schedule(new Task(), 10, TimeUnit.SECONDS);

// task to run after initial delay of 15 seconds at every 10 second interval
scheduledThreadPool.scheduleAtFixedRate(new Task(), 15, 10, TimeUnit.SECONDS);

// task to run after initial delay of 15 second at every 10 second after previous task completes
scheduledThreadPool.scheduleWithFixedDelay(new Task(), 15, 10, TimeUnit.SECONDS);

Thread Pool Parameters
Thread Pool corePoolSize maxPoolSize keepAliveTime QueueType
FixedTheadPool constr-arg same as core NA LinkedBlockingQueue
SingleThreadExecutor 1 1 NA LinkedBlockingQueue
CachedThreadPool 0 Integer.MAX_VALUE 60s SynchronousQueue
ScheduledThreadPool constr-arg Integer.MAX_VALUE 60s DelayedWorkQueue

Custom Thread Pool

If you do not want to use any out of the box available thread pools. You can always create your customize thread pool as below:-

// custom thread pool 
ExecutorService customThreadPool = 
	// (corePoolSize: 10, maxPoolSize: 100, keepAliveTime: 120s, QueueType: ArrayBlockingQueue)
	new ThreadPoolExecutor(10, 100, 120, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30));

How to customize a thread in Executor Thread Pool?

You can implement the newThread() method of class ThreadFactory to spawn custom threads in Executor Thread Pool.

Let’s Generate custom threads and change their names to Test_Thread_<Number>:-

public class ExecuterTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newFixedThreadPool(5, new ExecutorThreadFactory("Test_Thread_"));
		for(int i=1; i<=5; i++){
			service.execute(new Task());
		}		
	}
}

class Task implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());		
	}
}

class ExecutorThreadFactory implements ThreadFactory {
	
	AtomicInteger counter = new AtomicInteger();
	
	String threadNamePrefix;
	
	ExecutorThreadFactory(String prefix){
		this.threadNamePrefix = prefix;
	}

	@Override
	public Thread newThread(Runnable r) {
		Thread thread = new Thread(r, threadNamePrefix + counter.incrementAndGet());
		thread.setDaemon(false);
		return thread;
	}	
}
Output
Test_Thread_1 Test_Thread_4 Test_Thread_3 Test_Thread_2 Test_Thread_5

Runnable vs Callable Interface

Runnable and Callable interfaces are used to create a task and submit to thread pool for processing.

Runnable Interface Callable Interface
has run() method has call() method
run() do not return value call() return value
run() cannot throw checked exception call() can throw checked exception
execute Runnable task using ExecutorService execute() method submit Callable task using ExecutorService submit() method
execute() do not return anything submit() return Future object
public class RunnableVsCallable {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService service = Executors.newSingleThreadExecutor();
		
		// execute runnable task which return nothing
		service.execute(new RunnableTask());
		
		// submit callable task which return Future object
		Future<Integer> future = service.submit(new CallableTask());
		
		// get result from future object which is blocking operation
		Integer result = future.get();
		
		// shutdown the thread gracefully
		service.shutdown();
	}

	static class RunnableTask implements Runnable {

		@Override
		public void run() {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	static class CallableTask implements Callable<Integer> {
		
		@Override
		public Integer call() throws InterruptedException {
			Thread.sleep(1000);
			return new Random().nextInt();
		}
	}
}

Synchronized Block vs Reentrant Lock

  • Reentrant locks are explicit, whereas Synchronized block is implicit
  • Reentrant locks provides flexibility to lock/unlock in any scopes, whereas Synchronized block scope is limited to curly braces {}
    In below example lock() is outside and unlock() is inside the try-finally block.
    private static ReentrantLock lock = new  ReentrantLock();
    
    private static void accessResource() {
      lock.lock();
      try{
        // access the resource
      }finally{
        lock.unlock();
      }
    }
    
    public static void main(String[] args){
      Thread t1 = new Thread(() -> accessResource)); t1.start();
      Thread t2 = new Thread(() -> accessResource)); t2.start();
      Thread t3 = new Thread(() -> accessResource)); t3.start();
    }
    
    The same accessResource() method using Synchronized block:
    private static void accessResource() {
      synchronized(this) {  // equivalent to lock.lock() 
        // access the resource
      }                     // equivalent to lock.unlock() 
    }
    
  • Reentrant locks lock() can be called multiple times without calling unlock() method hence the name reentrant.
    Calling lock() two times means lock inside lock. You need to call unlock() two times as well to come out of the lock completely.
  • Reentrant locks are unfair by default. You can enable fairness by passing boolean true new ReentrantLock(true) in constructor. Fairness provides more chance to acquire lock to the threads waited for longest time in the queue. Performance is slow when fairness is turned on.
  • Reentrant locks provide ability to tryLock() and tryLock(timeout)
    private static void accessResource() {
      boolean lockAcquired = lock.tryLock();
      // boolean lockAcquired = lock.tryLock(5, TimeUnit.SECONDS);
      if(lockAcquired) {
        lock.lock();
        try{
          // access the resource
        }finally{
          lock.unlock();
        }
      } else {
        // do something else
      }   
    }
    

Reentrant Lock vs ReadWrite Lock

  • ReentrantLock consist of only one lock, only one thread can acquire that lock at a time
  • ReentrantReadWriteLock consist of ReadLock and WriteLock. Though they are two separate instances, either read or write operation is allowed at a time.
    Either one thread can acquire a write lock or multiple threads can acquire a read lock at a time.

Both ReadLock and WriteLock use the same Queue behind the scene waiting for their turn.

public class ReadWriteLock {

	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
	private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
	
	private void readResource() {
		readLock.lock();
		// read the resource
		readLock.unlock();
	}
	
	private void writeResource() {
		writeLock.lock();
		// write the resource
		writeLock.unlock();
	}
	
	public static void main(String[] args) {
		ReadWriteLock obj = new ReadWriteLock();
		Thread t1 = new Thread(() -> obj.readResource()); t1.start();
		Thread t2 = new Thread(() -> obj.readResource()); t2.start();
		Thread t3 = new Thread(() -> obj.writeResource()); t3.start();
		Thread t4 = new Thread(() -> obj.writeResource()); t4.start();
		Thread t5 = new Thread(() -> obj.readResource()); t5.start();

		//t1,t2 can read at the same time  
		//t3,t4 can not write when t1,t2 are reading  
		//t4 can not write when t3 is writing  
		//t5 can not read when t3 is writing 
	}	
}

What is volatile keyword?

Each thread runs in one core of multi-core processor. Each Core has their own local cache. All the cores share one shared cache.

   thread-1       thread-2
┌―――――――――――――┐―――――――――――――┐
|   Core 1    |   Core 2    | 
|―――――――――――――|―――――――――――――|
| Local Cache | Local Cache |
|―――――――――――――┘―――――――――――――|
|       Shared Cache        |  
└―――――――――――――――――――――――――――┘

When you apply volatile keyword to a property. Any updates in that property done by thread-1 in local-cache is pushed down to shared-cache to make sure that the update is visible to thread-2

// atomic operation
volatile boolean flag  = true;

volatile keyword doesn’t work when you do compound operations such as count++ which is read, increment and write back. In such case we can use AtomicInteger or AtomicLong

Type Use Case
volatile Flags
AtomicInteger, AtomicLong Counters

What is ThreadLocal Object?

ThreadLocal object is used to create object per thread instances for memory efficiency and thread-safety.

For example, SimpleDateFormat is not a thread-safe object and it is an expensive object to create every time we use it. It is a good candidate to create as a ThreadLocal object.

We created a fixed thread pool of 10 threads and submitted 1000 tasks. In this case, 10 ThreadLocal<SimpleDateFormat> objects will be created, one for each thread.

formatter.get() method make sure that it returns the object of the currently running thread out of those 10 formatter objects.

// create SimpleDateFormat ThreadLocal object and initialize with value
class ThreadSafeFormatter {
  public static ThreadLocal<SimpleDateFormat> formatter = 
		ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
}

public class UserService {
  private static ExecutorService threadPool = Executors.newFixedThreadPool(10);

  public static void main(String[] args){
	// submit 1000 tasks
    for(int i=0; i<1000; i++){
      threadPool.submit(() -> new UserService().birthDate(i));
    }
  }

  public String birthDate(int userId){
    Date birthDate = birthDateFromDb(userId);

	// get formatter object of currently running thread
    final SimpleDateFormat formatter = ThreadSafeFormatter.formatter.get();

    return formatter.format(birthDate);
  }
}

What is Semaphore?

Semaphore is used when you want to limit maximum number of concurrent calls to a particular resource in multi-threaded environment.

public class Semaphore {
	public void acquire();
	public void release();
}

Semaphore(3) means only 3 threads can acquire() a lock and use resources at a time. As soon as one thread out of 3 release() the lock, 4th thread can acquire() the lock and use the resources.

public class SemaphoreTest {

	public static void main(String[] args) throws InterruptedException {		
		// ThreadPool of 50 threads
		ExecutorService service = Executors.newFixedThreadPool(50);
		
		// Semaphore to rum max 3 concurrent calls
		Semaphore semaphore = new Semaphore(3);
		
		// Execute 1000 tasks (initiate 1000 threads) to test
		IntStream.of(1000).forEach(i -> service.execute(new Task(semaphore)));
		
		// Shutdown the ExecutorService gracefully
		service.shutdown();
		service.awaitTermination(1, TimeUnit.MINUTES);
	}

	static class Task implements Runnable {

		private Semaphore semaphore;

		Task(Semaphore semaphore) {
			this.semaphore = semaphore;
		}

		@Override
		public void run() {		
			// Start code -> can be executed by 50 threads concurrently 
			// since thread pool size is 50

			try {
			// Only 3 threads can acquire lock at a time since semaphore count is 3
			// Other threads wait here.
			semaphore.acquire();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Slow Code -> Heavy operations like IO 
			// can be executed by only 3 threads

			// Release the lock for other threads to come in Slow code
			semaphore.release();

			// End code -> can be executed by 50 threads concurrently 
			// since thread pool size is 50
		}
	}
}

What is CountDownLatch?

CountDownLatch is used when we want our main thread to wait until all the dependent services are initialized and up.

public class CountDownLatch {
	public void countDown();
	public void await();
}
  1. Create a latch = CountDownLatch(3) means three services to be initialized before main thread.
  2. Each service call latch.countDown() after initializing which decrement the countdown by 1.
  3. Main thread waits for all services to be initialized (until countdown reaches zero) using latch.await().
  4. Once all 3 services are initialized (countdown reaches zero), main thread start doing its job.
public class CountDownLatchTest {

	public static void main(String[] args) throws InterruptedException{
		CountDownLatch latch = new CountDownLatch(3);

		Thread cacheService = new Thread(new Service("Cache Service", latch));
		Thread alertService = new Thread(new Service("Alert Service", latch));
		Thread validationService = new Thread(new Service("Validation Service", latch));
		
		cacheService.start();
		alertService.start();
		validationService.start();
		
		latch.await(); //wait here till latch count reaches 0 then only process next line of code.
		System.out.println("All Services are up and running. Main Thread started processing...");	
	}
	
	static class Service implements Runnable{
		
		private String name;
		private CountDownLatch latch;
		
		Service(String name, CountDownLatch latch){
			this.name = name;
			this.latch = latch;
		}

		@Override
		public void run() {
			// startup task
			System.out.println(name + " is up");
			latch.countDown();
			// continue w/ other tasks
		}		
	}
}
Output
Cache Service is up Validation Service is up Alert Service is up All Services are up and running. Main Thread started processing...

How to start 5 threads at the same time?

Well, you can have max number of parallel threads run at the same time is equal to number of cores in CPU. If you have a 4 cores CPU, only 4 threads can start at the exact same time.

Though the idea of the question is if you have any Java concurrency feature which allows you to trigger the threads at the same time. Answer is CountDownLatch.

  1. Create a CountDownLatch of 5
  2. Create and start all the 5 threads
  3. All the threads wait until CountDownLatch reaches zero
  4. All the threads start at the same time from that point
public class StartThreadsAtSameTime {

	public static void main(String[] args) throws InterruptedException {
		// Initialize Countdown = 5
		CountDownLatch latch = new CountDownLatch(5);

		// Initialize and start 5 threads passing the same latch object
		for (int i = 0; i < 5; i++) {
			Thread t = new Thread(new Task(latch));
			t.start();
		}

		System.out.println("All threads wait until Countdown = 0");

		// Decrement the Countdown 5 times
		for (int i = 0; i < 5; i++) {
			latch.countDown();
		}

		// All threads start printing at this point
	}

	static class Task implements Runnable {

		private CountDownLatch latch;
		Task(CountDownLatch latch) { this.latch = latch;}

		@Override
		public void run() {
			try {
				// thread wait for Countdown = 0
				latch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " is running");
		}
	}
}
Output
All threads wait until Countdown = 0 Thread-2 is running Thread-1 is running Thread-3 is running Thread-0 is running Thread-4 is running

What is CyclicBarrier?

CyclicBarrier is used when two or more threads are required to reach at certain barrier point.

  1. They wait for each other at barrier point using await().
  2. Once all reach at barrier point, they start from there again.
  3. Since it is a CyclicBarrier, at this point, cycle is reset.
  4. All three threads wait again at next barrier point using await()
  5. Once all reach at next barrier point, they start from there again.
  6. This cycle goes on…
public class CyclicBarrierTest {

	public static void main(String[] args) {
		CyclicBarrier barrier = new CyclicBarrier(3);
		Thread t1 = new Thread(new Task(barrier));
		Thread t2 = new Thread(new Task(barrier));
		Thread t3 = new Thread(new Task(barrier));
		t1.start();
		t2.start();
		t3.start();
	}

	static class Task implements Runnable {

		private CyclicBarrier barrier;

		Task(CyclicBarrier barrier) { this.barrier = barrier;}

		@Override
		public void run() {
			while(true) {
				String threadName = Thread.currentThread().getName();
				System.out.println(threadName + " running towards barrier");
				
				try {
					barrier.await();  // wait for other threads to reach at barrier point
				} catch (InterruptedException | BrokenBarrierException e) {
					e.printStackTrace();
				}
        
				// barrier cycle is reset at this point

				System.out.println(threadName + " crossed barrier");				
				try {
					Thread.sleep(1000); // just sleep for a while to verify in console
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}		
		}
	}
}
Output
Thread-0 running towards barrier Thread-2 running towards barrier Thread-1 running towards barrier Thread-1 crossed barrier Thread-0 crossed barrier Thread-2 crossed barrier Thread-1 running towards barrier Thread-2 running towards barrier Thread-0 running towards barrier Thread-0 crossed barrier Thread-1 crossed barrier Thread-2 crossed barrier ... ... ...

Retrieve Price from N Sources Asynchronously

We can implement a Scatter Gather Pattern using CompletableFuture where we run three tasks asynchronously to fetch price of a product from 3 websites.

Note that when you call CompletableFuture.runAsync(Runnable runnable) then Java runs this in a separate thread of ForkJoinPool.commonPool() thread pool by default. You do not need to create any separate thread pool.

Also note that CompletableFuture.get() method is blocking operations. We do not want our pricing result to wait forever if any website is down thatswhy we have given a timeout of 5 seconds.

We wait for them for max 5 seconds and return the prices. Two things can happen:-

  1. If all three tasks completed (run method executed) within 4 seconds, prices from all websites are returned at 4th second.
  2. If only two tasks are completed within 5 seconds, prices from only two websites are returned at 5th second.

public class ScatterGatherPattern {

	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		ScatterGatherPattern scatterGather = new ScatterGatherPattern();
		scatterGather.getPrices(1);
	}

	private Set<Double> getPrices(int productId) throws InterruptedException, ExecutionException, TimeoutException {
		Set<Double> prices = Collections.synchronizedSet(new HashSet<>());
		
		CompletableFuture<Void> task1 = CompletableFuture.runAsync(new Task("amazon.com", productId, prices));
		CompletableFuture<Void> task2 = CompletableFuture.runAsync(new Task("ebay.com", productId, prices));
		CompletableFuture<Void> task3 = CompletableFuture.runAsync(new Task("wallmart.com", productId, prices));
		CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2, task3);

		// wait for all the taks to complete, but max for 5 seconds
		allTasks.get(5, TimeUnit.SECONDS); //blocking operation

		return prices;
	}

	private static class Task implements Runnable {
		private String url;
		private int productId;
		private Set<Double> set;

		Task(String url, int productId, Set<Double> set) {
			this.url = url;
			this.productId = productId;
			this.set = set;
		}

		@Override
		public void run() {
			double price = 0;
			// make http call (url, productId) to get price
			set.add(price);
		}
	}
}

Also Read Async Fetch Data from N Sources and Combine In Java

Future vs CompletableFuture

Future CompletableFuture
Introduced in Java 5 (2004) Introduced in Java 8 (2014)
ExecuterService.submit() returns a Future object CompletableFuture.supplyAsnc() returns CompletableFuture object
Futures cannot be chained together CompletableFutures can be chained to perform async operations

What is DeadLock. How to Detect and Avoid them?

Deadlock is a situation where:-

  1. thread_1 acquires lock_A and waiting to acquire lock_B
  2. thread_2 acquires lock_B and waiting to acquire lock_A

Now both thread_1 and thread_2 will wait for each other indefinitely and result into deadlock situation.

public class DeadlockTest {
	
	private Lock lockA = new ReentrantLock();
	private Lock lockB = new ReentrantLock();

	public static void main(String[] args){
		DeadlockTest test = new DeadlockTest();
		test.execute();
	}
	
	private void execute() {
		new Thread(this::processThis).start();
		new Thread(this::processThat).start();
	}
	
	private void processThis() {
		lockA.lock();
		System.out.println(Thread.currentThread().getName() + " processing resource A");
		
		lockB.lock();
		System.out.println(Thread.currentThread().getName() + " processing resource B");
			
		lockA.unlock();
		lockB.unlock();
	}
	
	private void processThat() {
		lockB.lock();
		System.out.println(Thread.currentThread().getName() + " processing resource B");
		
		lockA.lock();
		System.out.println(Thread.currentThread().getName() + " processing resource A");
				
		lockA.unlock();
		lockB.unlock();
	}
}

How to detect a DeadLock?

Deadlock can be detected at runtime by creating Thread dump of the application which represent the state of the application at that point of time.

We can use tools such as VisualVM, JProfiler, YourKit to get the thread dump. We can also use any of the following commands to get the thread dump:-

kill -3 <process_id>

jstack <process_id> > ./out.txt

Alternatively we can run this program constantly in the background to check for deadlocks:-

private static void detectedDeadLock() {
	ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
	long[] threadIds = threadBean.findDeadlockedThreads();
	boolean deadLock = threadIds != null && threadIds.length > 0;
	System.out.println("DeadLock found: " + deadLock);
}

How to avoid a DeadLock?
  • Use timeouts while acquiring locks if possible, so that thread do not wait for indefinitely
Lock lock = new ReentrantLock();
lock.tryLock(2, TimeUnit.SECONDS);
  • If same locks are used in different methods, try to acquire the locks in same order.
private void processThis() {
	lockA.lock();
	lockB.lock();
	// ...
}

private void processThat() {
	lockA.lock();
	lockB.lock();
	// ...
}

Parallelism vs Concurrency

Parallelism Concurrency
Doing a lot of things at once Dealing with lot of things at once
If your machine is having 4 core CPU then you can run at most 4 tasks in parallel If your Java ThreadPool size is 20 then you can run at most 20 tasks concurrently in different threads
If you have 1 core CPU, you can not achieve Parallelism If you have 1 core CPU, you can still achieve Concurrency
If you have ThreadPool of size 1, you can still achieve Parallelism If you have ThreadPool of size 1, you can not achieve Concurrency

Let’s look at various use cases to understand the difference:-

Concurrent, but not Parallel

If machine is having 1 core CPU and you are using ThreadPool of size 20. Say you submit 20 similar tasks to ThreadPool.

At any moment, all the 20 similar tasks are in progress (concurrency=20), and only one tasks is actually running on CPU (parallelism=1) while others are waiting.

Parallel, but not Concurrent

If machine is having 4 core CPU and you are using 20 ThreadPools of size 1. Say you submit 20 different tasks to each ThreadPool.

At any moment, all the 20 different types of tasks are in progress, and only 1 similar type of task in progress (concurrency=1), and 4 tasks are actually running on CPU (parallelism=4) while others are waiting.

Please note that we say tasks are running concurrently only when they are similar tasks, or they are accessing/updating same resources, or they need to coordinate.

Not Parallel, not Concurrent

If machine is having 1 core CPU and you are using ThreadPool of size 1. Say you submit 20 similar tasks to ThreadPool.

At any moment, Only 1 task is in progress (concurrency=1), and only one tasks is actually running on CPU (parallelism=1) while no one is waiting.

How to handle exceptions thrown from ExecutorService tasks?

There are multiple ways to handle the exceptions thrown by tasks executed by ExecutorService:-

1. Use Callable instead of Runnable

Callable swallow the exception and throw ExecutionException when future.get() gets called. We can catch the exception at this point and do something about it!

public void submitThenThrowUncheckedThenGet() {
	final ExecutorService executorService = Executors.newFixedThreadPool(10);
	final Future<Object> future = executorService.submit(() -> {
		throw new RuntimeException("Uncaught exception in callable task");
	});

	try {
		future.get();
	} catch (ExecutionException | InterruptedException e) {
		System.out.println("Caught the exception ( " + e.getMessage() + " )");
	}

	executorService.shutdown();
}
Output
Caught the exception ( java.lang.RuntimeException: Uncaught exception in callable task )
2. Handle with UncaughtExceptionHandler

Register the threads spawned from ThreadFactory with UncaughtExceptionHandler. This handler will get called everytime a registered thread come across an uncaught exceptions. Now use this ThreadFactory to initialize ExecutorService

public static class AppExceptionHandler implements Thread.UncaughtExceptionHandler {

	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println("Caught the exception ( " + e.getMessage() + " ) thrown by thread [" + t.getName() + "]");
	}
}

public static class AppThreadFactory implements ThreadFactory {

    @Override
    public Thread newThread(Runnable r) {
        final Thread thread = new Thread(r);
        thread.setUncaughtExceptionHandler(new AppExceptionHandler());
        return thread;
    }
}

public void executeThenThrowUnchecked() {
    final ExecutorService executorService = Executors.newFixedThreadPool(1, new AppThreadFactory());
    
    executorService.execute(() -> {
        throw new RuntimeException("Uncaught exception in runnable task");
    });

    executorService.shutdown();
}
Output
Caught the exception ( Uncaught exception in runnable task ) thrown by thread [Thread-0]
3. Handle with overriding afterExecute in ThreadPoolExecutor

Create custom ThreadPoolExecutor and override afterExecute method to check for exceptions. Now use this ThreadPoolExecutor to initialize ExecutorService

public static class MonitoringThreadPoolExecutor extends ThreadPoolExecutor {

	public MonitoringThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
										BlockingQueue<Runnable> workQueue) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
	}

	@Override
	protected void afterExecute(Runnable r, Throwable t) {
		super.afterExecute(r, t);
		if(t != null){
			System.out.println("Caught the exception ( " + t.getMessage() + " )");
		}
	}
}
public void executeWithCustomThreadPoolExecutorThenThrowUnchecked() {
	final ExecutorService executorService = new MonitoringThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
		new LinkedBlockingQueue<>());
	executorService.execute(() -> {
		throw new RuntimeException("Uncaught exception in runnable task");
	});

	executorService.shutdown();
}
Output
Caught the exception ( Uncaught exception in runnable task ) Exception in thread "pool-1-thread-1" java.lang.RuntimeException: Uncaught exception in runnable task at com.example.thread.ExceptionHandlingInThread.lambda$executeWithCustomThreadPoolExecutorThenThrowUnchecked$2(ExceptionHandlingInThread.java:76) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834)