Java Threading question
Created on: Oct 13, 2024
- Print even and odd number using two threads. Even number for one thread and odd number for another thread.
package org.learning.misc; class EvenOddPrint { int start; int end; public EvenOddPrint(int start, int end) { this.start = start; this.end = end; } public boolean printEven() { synchronized (this) { while (start < end) { while (start % 2 == 1) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); return false; } } System.out.println("Printing even " + start++ + " using thread " + Thread.currentThread().getName()); notify(); } } return true; } public boolean printOdd() { synchronized (this) { while (start <= end) { while (start % 2 == 0) { try { wait(); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); return false; } } System.out.println("printing odd " + start++ + " using thread " + Thread.currentThread().getName()); notify(); } return false; } } } public class Test { public static void main(String[] args) throws InterruptedException { EvenOddPrint evenOddPrint = new EvenOddPrint(1, 10); Thread evenThread = new Thread(new Runnable() { @Override public void run() { evenOddPrint.printEven(); } }); Thread oddThread = new Thread(() -> evenOddPrint.printOdd()); oddThread.start(); evenThread.start(); evenThread.join(); oddThread.join(); } }
-
Callable interface, FutureTask class, Future class
Callable is an interface in Java that defines a single method called call(). Callable is primarily used to execute a task in a separate thread and retrieve the result of that task once it's completed.
A FutureTask is a class in Java that represents a task that will be executed in a separate thread. We then create a Thread object and pass the FutureTask object to its constructor.
Future is a class in Java that represents the result of a computation that will be completed in the future.
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.stream.IntStream; class CallSum implements Callable<Integer> { int n; public CallSum(int n) { this.n = n; } @Override public Integer call() throws Exception { return IntStream.rangeClosed(1, n).sum(); } } public class Test { public static void main(String[] args) throws InterruptedException, ExecutionException { CallSum callSum = new CallSum(10); FutureTask<Integer> futureTask = new FutureTask<>(callSum); Thread thread = new Thread(futureTask); thread.start(); System.out.println(futureTask.get()); } }
- Difference between thread in process.
| Feature | Process | Thread |
|---|---|---|
| Definition | Process means any program is in execution. | A subset of a process |
| Dependency | Independent | Dependent on the parent process |
| Address Space | Separate address space | Shared address space with other threads |
| Context Switching | Slower compared to threads | Faster compared to processes |
| Effect of Parent on Child | Changes in the parent process do not affect child process | Changes in the parent thread can affect child thread |
-
Difference between wait and sleep
wait() sleep() 1) The wait() method is defined in Object class. The sleep() method is defined in Thread class. 2) The wait() method releases the lock. The sleep() method doesn't release the lock. -
daemon threads Daemon threads are low-priority threads that run in the background to perform tasks such as garbage collection or provide services to user threads. JVM terminates demon thread when all the user thread finish their execution.
-
Suppose you have 4 threads in which one thread say t1 is using the resource. After completing the task, t1 will call notifyAll and other thread are also waiting to use the resource. what will happen in this case. In above case, t1 will notify all other thread t2, t3, t4 and they will enter into runnable state. Lock will be released on t1 and let's say t2 acquire the lock based on thread scheduling which is FIFO and thread priority. Similarly cycle will continue.
-
What is inter-thread communication ? Communication between synchronized threads is referred to as inter-thread communication. Some common functions used to perform inter-thread communication in Java are — notify(), wait(), and notifyAll().
-
context switching Context switching is a feature through which the current state of a thread is saved for it to be restored and executed later.
-
Java code to Deadlock in Java Multithreading
class Resource { private String name; public Resource(String name) { this.name = name; } public String getName() { return name; } } class ThreadA extends Thread { private Resource resource1; private Resource resource2; public ThreadA(Resource resource1, Resource resource2) { this.resource1 = resource1; this.resource2 = resource2; } public void run() { synchronized (resource1) { System.out.println("Thread A: Holding " + resource1.getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread A: Waiting for " + resource2.getName()); synchronized (resource2) { System.out.println("Thread A: Acquired " + resource2.getName()); } } } } class ThreadB extends Thread { private Resource resource1; private Resource resource2; public ThreadB(Resource resource1, Resource resource2) { this.resource1 = resource1; this.resource2 = resource2; } public void run() { synchronized (resource2) { System.out.println("Thread B: Holding " + resource2.getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread B: Waiting for " + resource1.getName()); synchronized (resource1) { System.out.println("Thread B: Acquired " + resource1.getName()); } } } } public class Solution { public static void main(String[] args) { Resource resource1 = new Resource("Resource 1"); Resource resource2 = new Resource("Resource 2"); ThreadA threadA = new ThreadA(resource1, resource2); ThreadB threadB = new ThreadB(resource1, resource2); threadA.start(); threadB.start(); } }public class DeadlockExample { public static void main(String[] args) { Object lock1 = new Object(); Object lock2 = new Object(); Thread thread1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread 1: Acquired lock 1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("Thread 1: Acquired lock 2"); } } }); Thread thread2 = new Thread(() -> { synchronized (lock2) { System.out.println("Thread 2: Acquired lock 2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("Thread 2: Acquired lock 1"); } } }); thread1.start(); thread2.start(); } } -
How to avoid deadlock situations ? i. By way of avoiding nested thread locks and providing locks to only one thread at a time ii. By using thread join
-
Difference between ReentrantLock and ReadWriteLock ReentrantLock:
- General-purpose lock: Provides exclusive access to a shared resource.
- Reentrancy: Allows a thread to acquire the lock multiple times within the same block.
- Fairness: Can be configured to be fair or unfair.
- Additional methods: Offers methods like
tryLock(),tryLock(long timeout, TimeUnit unit), andlockInterruptibly()for more granular control.
ReadWriteLock:
- Specialized lock: Provides separate read and write locks for a shared resource.
- Multiple readers: Allows multiple threads to simultaneously read the resource.
- Exclusive writer: Only one thread can write to the resource at a time.
- Fairness: Can be configured for fairness or unfairness for both read and write locks.
- Separate methods: Provides methods like
readLock()andwriteLock()to acquire read and write locks, respectively.
Key Differences:
Feature ReentrantLock ReadWriteLock Purpose General-purpose lock Specialized lock for read/write operations Locking mechanism Exclusive access Separate read and write locks Multiple readers No Yes Exclusive writer Yes Yes Methods lock(),unlock(),tryLock(), etc.readLock(),writeLock(),tryReadLock(), etc.When to Use Which:
- ReentrantLock: Use when you need general-purpose locking without the need for separate read and write operations. This is suitable for scenarios where only exclusive access is required.
- ReadWriteLock: Use when you have a shared resource that is frequently read but infrequently written. This allows multiple readers to access the resource simultaneously, improving performance.
-
How do you debug threading issues such as deadlocks or race conditions in a production system?
- Thread dump can be used and looks for thread which are in BLOCKED and WAITING state.
- Logging with threadid or unique name with details
- Using tools like
- JConsole or VisualVM
- Java Flight Recorder (JFR) and Java Mission Control (JMC)
- Avoid Nested Locks
-
How to gracefully stop thread if we are using Executor thread.
- shutdown(): Allows previously submitted tasks to complete but does not accept new tasks.
- shutdownNow(): Attempts to stop all actively executing tasks and halts the processing of waiting tasks.
-
Print the square of number from 1 to n using executor service.
import java.util.List; import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.Function; class Test { public static void main(String[] args) throws Exception{ int n = 1000; ExecutorService executor = Executors.newFixedThreadPool(10); List<Future<Integer>> futures = new ArrayList<>(); for (int i = 1; i <= n; i++) { final int number = i; Callable<Integer> task = () -> number * number; futures.add(executor.submit(task)); } for (Future<Integer> future : futures) { System.out.println(future.get()); } executor.shutdown(); } } -
What is thread poll. What are the types of thread pool present in Executor service.
A thread pool is a software design pattern that manages a collection of threads to perform tasks concurrently in a computer program
A thread pool reuses previously created threads to execute current tasks and offers a solution to the problem of thread cycle overhead and resource thrashing. Since the thread is already existing when the request arrives, the delay introduced by thread creation is eliminated, making the application more responsive.
Executor service keep our code decoupled from the actual implementation of thread pool.
- newFixedThreadPool
- newCachedThreadPool
- newSingleThreadExecutor
- ScheduledThreadPoolExecutor
- ForkJoinPool
-
What is thread local ?
The main idea of ThreadLocal in Java is to provide thread-local storage, enabling each thread to have its own, isolated copy of a variable. This ensures that the value stored in a ThreadLocal variable is accessible only to the thread that set it, making it useful for maintaining thread-specific context without requiring synchronization.
-
Difference between fixed and cached thread pool.
Feature FixedThreadPool CachedThreadPool Thread Count Fixed and pre-defined (e.g., 3 threads). Dynamic; grows as needed, no upper limit. Task Queue Unbounded queue; tasks wait if threads are busy. No queue; tasks directly create threads if needed. Idle Threads Kept alive indefinitely. Terminated after 60 seconds of inactivity. Overhead Predictable resource usage. Higher resource usage if too many tasks are submitted. Best Use Case Limited or predictable tasks. Short-lived, unpredictable, or high-volume tasks. Example Use Cases Database queries, file processing. Handling incoming web requests, real-time tasks. -
Why do we use executor framework if we create thread normally.
- Scalability: Managing a large number of threads manually is complex and error-prone.Provides built-in thread pools (FixedThreadPool, CachedThreadPool, etc.) to handle scalability.
- Better communication between threads
- shutdown provide capability for completion of all thread assigned work
- fork - join framework for parallel processing
- Ease of use: Simplifies thread management.
- Performance optimization: Reuses threads via pooling.
- Flexibility: Supports advanced features like scheduling, returning results, and managing task rejection.
-
Executor service main points.
- Runnable‘s single method does not throw an exception and does not return a value. The Callable interface may be more convenient, as it allows us to throw an exception and return a value.
-
Different implementation of
ExecutorService?- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
- ForkJoinPool
-
What is difference between
ForkJoinvsThreadPoolExecutorForkJoinPool
- Designed for: Divide-and-conquer algorithms where tasks can be recursively broken down into smaller subtasks.
- Work-Stealing Algorithm: Efficiently distributes tasks among available threads, leading to optimal resource utilization.
- Ideal for: CPU-bound tasks, large data processing, and recursive algorithms.
ThreadPoolExecutor
- Designed for: General-purpose task execution, including both CPU-bound and I/O-bound tasks.
- Fixed Thread Pool: Maintains a fixed number of threads, which can be configured to handle different workloads.
- Ideal for: Handling a large number of short-lived tasks, controlling resource usage, and scheduling tasks.
-
When we should use
ForkJoinPoolframework.- Divide-and-Conquer Algorithms: Problems that can be recursively broken down into smaller subproblems.
- CPU-Bound Tasks: Tasks that heavily utilize CPU resources.
- Large Datasets: Processing large amounts of data in parallel.
-
Important point in
fork joinframeworkfork():the task is scheduled for execution in one of the worker threads managed by the ForkJoinPool. Arranges to asynchronously execute this task in the pool the current task is running.join: Returns the result of the computation when it is done.
-
What is difference between
joinandgetmethod inAspect join()get()Exception Handling Wraps exceptions in RuntimeExceptionThrows InterruptedExceptionandExecutionException.Performance Optimized for Fork/Join Framework. Slightly more overhead. Use Case Specific to Fork/Join tasks. General-purpose ( Futureinterface).Waiting Behavior Waits for the result without interruption. Allows handling of interruptions. -
Write a program using fork and join framework to calculate the sum of an array
arr[] = {1,2,3,4,5.......,1_00_000}import java.util.concurrent.RecursiveTask; import java.util.concurrent.ForkJoinPool; // RecursiveTask to calculate the sum of an array using ForkJoin Framework class SequentialSumTask extends RecursiveTask<Long> { private static final int THRESHOLD = 10_000; // Threshold for splitting the task private final int[] array; private final int start; private final int end; // Constructor public SequentialSumTask(int[] array, int start, int end) { this.array = array; this.start = start; this.end = end; } @Override protected Long compute() { // If the task is small enough, compute sequentially if ((end - start) <= THRESHOLD) { long sum = 0; for (int i = start; i < end; i++) { sum += array[i]; } return sum; } else { // Split the task into two subtasks int mid = (start + end) / 2; SequentialSumTask leftTask = new SequentialSumTask(array, start, mid); SequentialSumTask rightTask = new SequentialSumTask(array, mid, end); // Fork the subtasks leftTask.fork(); long rightResult = rightTask.compute(); long leftResult = leftTask.join(); // Combine the results return leftResult + rightResult; } } } public class ForkDemo { public static void main(String[] args) { // Create a large array of integers int size = 100_000; int[] array = new int[size]; for (int i = 0; i < size; i++) { array[i] = i + 1; // Fill the array with values from 1 to size } // Create a ForkJoinPool ForkJoinPool pool = new ForkJoinPool(); // Create the main task SequentialSumTask task = new SequentialSumTask(array, 0, array.length); // Start the task and get the result long startTime = System.currentTimeMillis(); long result = pool.invoke(task); long endTime = System.currentTimeMillis(); System.out.println("Sum: " + result); System.out.println("Time taken: " + (endTime - startTime) + " ms"); } }Sum: 5000050000 Time taken: 4 ms -
Write a program to calculate fibonacci series using fork and join framework.
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; class Fibonacci extends RecursiveTask<Long> { private final long n; Fibonacci(long n) { this.n = n; } @Override protected Long compute() { if (n <= 1) return n; Fibonacci f1 = new Fibonacci(n - 1); f1.fork(); Fibonacci f2 = new Fibonacci(n - 2); return f2.compute() + f1.join(); } } public class FibonacciForkJoin { public static void main(String[] args) { int processorCount = Runtime.getRuntime().availableProcessors(); System.out.println("Processor count "+ processorCount); ForkJoinPool forkJoinPool = new ForkJoinPool(); Fibonacci fibonacci = new Fibonacci(25); long res = forkJoinPool.invoke(fibonacci); System.out.println(res); System.out.println("Default pool size: " + ForkJoinPool.commonPool().getParallelism()); } }Processor count 8 Default pool size: 7 75025Above program runs on M1 processor MacBook with 8 core. In above program,the default ForkJoinPool reserves one processor for non-pool tasks.
-
What are thread pool ?
-
Difference between FixedThreadPool and cachedThreadPool.
topic to cover
- inter-thread communication
- preemptive scheduling and time slicing
