Java Concurrency Concepts Tutorial
1. What is the difference between the Runnable and Callable interfaces in Java?
Answer: The main difference is that Callable can return a result and throw exceptions, whereas Runnable cannot.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample implements Callable<String> {
public String call() throws Exception {
return "Hello from Callable!";
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> result = executorService.submit(new CallableExample());
System.out.println(result.get()); // Output: Hello from Callable!
executorService.shutdown();
}
}
2. Explain the difference between the synchronized and volatile keywords in Java.
Answer: Synchronized ensures exclusive access to a resource, while volatile guarantees visibility of changes across threads.
public class SynchronizedVsVolatile {
private static int counter = 0;
// Synchronized method
public synchronized void increment() {
counter++;
}
// Volatile variable
private volatile boolean flag = false;
public void setFlag() {
flag = true;
}
}
3. How can you achieve thread-safety in Java?
Answer: Thread-safety can be achieved using synchronization (e.g., synchronized methods or blocks), using concurrent data structures, or using the `java.util.concurrent` library.
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadSafetyExample {
private int nonAtomicCounter = 0;
private AtomicInteger atomicCounter = new AtomicInteger(0);
// Synchronized method
public synchronized void incrementNonAtomicCounter() {
nonAtomicCounter++;
}
// Atomic operation
public void incrementAtomicCounter() {
atomicCounter.incrementAndGet();
}
}
4. What is the purpose of the `join` method in Java threads?
Answer: The `join` method is used to wait for a thread to complete its execution before proceeding to the next steps in the program.
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
// Some time-consuming task
});
Thread thread2 = new Thread(() -> {
// Another task
});
thread1.start();
thread2.start();
thread1.join(); // Wait for thread1 to finish
thread2.join(); // Wait for thread2 to finish
System.out.println("All threads have completed.");
}
}
5. Explain the concept of the `Executor` framework in Java concurrency.
Answer: The `Executor` framework provides a higher-level replacement for managing threads, allowing the separation of task submission and execution policies.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorFrameworkExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
final int taskNumber = i;
executorService.execute(() -> System.out.println("Task " + taskNumber + " executed by " + Thread.currentThread().getName()));
}
executorService.shutdown();
}
}
6. What is the purpose of the `synchronized` keyword in Java?
Answer: The `synchronized` keyword is used to control access to shared resources by multiple threads, ensuring that only one thread can access the critical section at a time.
public class SynchronizedExample {
private int sharedCounter = 0;
// Synchronized method
public synchronized void incrementCounter() {
sharedCounter++;
}
}
7. Explain the concept of the `ReentrantLock` in Java concurrency.
Answer: `ReentrantLock` is a synchronization mechanism that allows a thread to hold the lock multiple times, enabling reentrant locking.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private Lock lock = new ReentrantLock();
public void performTask() {
lock.lock();
try {
// Critical section
} finally {
lock.unlock();
}
}
}
8. What is the significance of the `volatile` keyword in Java?
Answer: The `volatile` keyword ensures that a variable's value is always read from and written to the main memory, preventing thread-local caching of the variable.
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
flag = true;
}
}
9. How does the `CountDownLatch` class work in Java concurrency?
Answer: `CountDownLatch` is a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
// Worker threads
new Thread(() -> {
// Task 1
latch.countDown();
}).start();
new Thread(() -> {
// Task 2
latch.countDown();
}).start();
// Main thread waits for tasks to complete
latch.await();
System.out.println("All tasks have completed.");
}
}
10. Explain the concept of the `BlockingQueue` interface in Java.
Answer: `BlockingQueue` is an interface that represents a queue that supports blocking operations, allowing threads to wait until space is available or an element is retrieved.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// Producer
new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// Consumer
new Thread(() -> {
try {
int value = queue.take();
System.out.println("Consumed: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
11. Explain the concept of the `Semaphore` in Java concurrency.
Answer: `Semaphore` is a synchronization construct that controls access to a shared resource by limiting the number of threads that can access it simultaneously.
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private Semaphore semaphore = new Semaphore(2); // Allow 2 permits
public void accessResource() throws InterruptedException {
semaphore.acquire(); // Acquire a permit
try {
// Access the shared resource
} finally {
semaphore.release(); // Release the permit
}
}
}
12. What is the purpose of the `CyclicBarrier` class in Java?
Answer: `CyclicBarrier` is a synchronization aid that allows a set of threads to wait for each other to reach a common barrier point, and then proceed together.
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
private static final int THREAD_COUNT = 3;
private CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT);
public void performTask() {
try {
// Task execution
barrier.await(); // Wait for other threads
} catch (Exception e) {
e.printStackTrace();
}
}
}
13. How can deadlock be prevented in a multi-threaded Java application?
Answer: Deadlock prevention involves strategies such as avoiding circular waits, using a consistent order when acquiring multiple locks, and setting a timeout for lock acquisition.
// Example of avoiding circular waits
public class DeadlockPreventionExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
// Critical section
synchronized (lock2) {
// Critical section
}
}
}
public void method2() {
synchronized (lock2) {
// Critical section
synchronized (lock1) {
// Critical section
}
}
}
}
14. What is the `ThreadLocal` class in Java?
Answer: `ThreadLocal` is a class that provides thread-local variables, allowing each thread to have its own copy of a variable without affecting other threads.
public class ThreadLocalExample {
private static final ThreadLocal<String> threadLocalVariable = new ThreadLocal<>();
public void setThreadLocalValue(String value) {
threadLocalVariable.set(value);
}
public String getThreadLocalValue() {
return threadLocalVariable.get();
}
}
15. Explain the purpose of the `Thread.sleep()` method in Java.
Answer: The `Thread.sleep()` method is used to temporarily pause the execution of a thread, allowing other threads to run or introducing a delay in the program.
public class ThreadSleepExample {
public static void main(String[] args) {
try {
System.out.println("Task 1");
Thread.sleep(2000); // Sleep for 2 seconds
System.out.println("Task 2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
16. How does the `Thread.yield()` method work in Java?
Answer: The `Thread.yield()` method is a hint to the scheduler that the current thread is willing to yield its current use of a processor, allowing other threads to run.
public class ThreadYieldExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 1: " + i);
Thread.yield(); // Yield to other threads
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 2: " + i);
Thread.yield(); // Yield to other threads
}
});
thread1.start();
thread2.start();
}
}
17. Explain the concept of the `ReadWriteLock` in Java concurrency.
Answer: `ReadWriteLock` is an interface that provides a way to implement a lock mechanism where multiple threads can read a resource simultaneously, but only one thread can write to the resource at a time.
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private int sharedResource = 0;
public int readFromResource() {
readWriteLock.readLock().lock();
try {
return sharedResource;
} finally {
readWriteLock.readLock().unlock();
}
}
public void writeToResource(int value) {
readWriteLock.writeLock().lock();
try {
sharedResource = value;
} finally {
readWriteLock.writeLock().unlock();
}
}
}
18. What is the purpose of the `ScheduledExecutorService` in Java?
Answer: `ScheduledExecutorService` is an interface that extends `ExecutorService` and provides methods to schedule tasks for future execution with specified delays or at fixed rates.
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
// Schedule a task to run every 1 second
executorService.scheduleAtFixedRate(() -> {
System.out.println("Task executed at " + System.currentTimeMillis());
}, 0, 1, TimeUnit.SECONDS);
}
}
19. Explain the concept of the `CompletableFuture` class in Java.
Answer: `CompletableFuture` is a class that represents a promise of a future result and provides a flexible way to compose, combine, and perform asynchronous operations.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(s -> s + " CompletableFuture")
.thenApply(String::toUpperCase);
System.out.println(future.get()); // Output: HELLO COMPLETABLEFUTURE
}
}
20. How does the `ForkJoinPool` class work in Java?
Answer: `ForkJoinPool` is a framework for parallelizing recursive tasks, particularly those that can be broken down into smaller independent tasks.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinPoolExample extends RecursiveTask<Integer> {
private static final int THRESHOLD = 5;
private int[] array;
private int start;
private int end;
public ForkJoinPoolExample(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= THRESHOLD) {
// Perform the computation
return 0;
} else {
int mid = (start + end) / 2;
ForkJoinPoolExample leftTask = new ForkJoinPoolExample(array, start, mid);
ForkJoinPoolExample rightTask = new ForkJoinPoolExample(array, mid, end);
// Fork tasks
leftTask.fork();
rightTask.fork();
// Join results
return leftTask.join() + rightTask.join();
}
}
}