Concurrency Interview question to print even & odd
?? Saral Saxena ??????
?11K+ Followers | Linkedin Top Voice || Associate Director || 15+ Years in Java, Microservices, Kafka, Spring Boot, Cloud Technologies (AWS, GCP) | Agile , K8s ,DevOps & CI/CD Expert
Many a times an interview would try to make an assessment of your depth on the subject. For example even if you are able to use some of the advanced constructs of java concurrency and asynchronous task execution , they would still want to understand the evolution of the multi threading an concurrency over the period of time. For example how many ways you can manage and create threads in Java.
However in order to understand and implement the concept of concurrency you may also need to understand how threading works and how can implement the concepts while solving a simple problem.
It is very important to note here that there is no substitute of Synchronization construct when we want multiple threads to talk to share the state of an object. Handling concurrency and creating multiple asynchronous tasks are entirely different concepts and achieves differnt use cases.
Problem:
One of the simples way to achieve that
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class EvenOddPrinter {
public static void main(String[] args) {
final int maxNumber = 20; // Define the max number to print to
ExecutorService executor = Executors.newFixedThreadPool(2); // Create a thread pool with two threads
Runnable printEven = () -> {
for (int i = 2; i <= maxNumber; i += 2) { // Start from 2 for even numbers
System.out.println("Even: " + i);
try {
Thread.sleep(100); // Sleep to simulate some computation and make the output more readable
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore the interrupted status
System.out.println("Thread interrupted: " + e.getMessage());
}
}
};
Runnable printOdd = () -> {
for (int i = 1; i <= maxNumber; i += 2) { // Start from 1 for odd numbers
System.out.println("Odd: " + i);
try {
Thread.sleep(100); // Sleep to simulate some computation and make the output more readable
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore the interrupted status
System.out.println("Thread interrupted: " + e.getMessage());
}
}
};
CompletableFuture<Void> evenFuture = CompletableFuture.runAsync(printEven, executor);
CompletableFuture<Void> oddFuture = CompletableFuture.runAsync(printOdd, executor);
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(evenFuture, oddFuture);
combinedFuture.join(); // Wait for both tasks to complete
executor.shutdown(); // Shutdown the executor service
}
}
Output:
Now Interview is trying to see if you can use threads in their essence without using advanced constructs.
More efficient way without completable Future.
To make the even-odd number printing program more efficient and more synchronized, we can utilize a shared object for coordination between the two threads, ensuring that the even and odd printing tasks take turns. Instead of sleeping, we’ll use wait() and notify() to efficiently manage thread execution. This approach reduces unnecessary waiting and makes the program more responsive.
领英推荐
public class EvenOddPrinterUsingThreads {
private static final Object lock = new Object();
private static final int MAX_NUMBER = 20;
private static int current = 1;
public static void main(String[] args) {
Thread evenThread = new Thread(() -> printEvenNumbers());
Thread oddThread = new Thread(() -> printOddNumbers());
evenThread.start();
oddThread.start();
}
private static void printEvenNumbers() {
while (current <= MAX_NUMBER) {
synchronized (lock) {
if (current % 2 == 1) { // If current is odd, wait
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else {
System.out.println("Even: " + current);
current++;
lock.notifyAll(); // Notify the other thread
}
}
}
}
private static void printOddNumbers() {
while (current <= MAX_NUMBER) {
synchronized (lock) {
if (current % 2 == 0) { // If current is even, wait
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else {
System.out.println("Odd: " + current);
current++;
lock.notifyAll(); // Notify the other thread
}
}
}
}
}
Output:
Key Improvements:
How It Works:
This approach is more efficient than the previous example because it eliminates unnecessary sleeping and ensures that the threads work in perfect alternation, thus making the program more responsive and quicker in execution.
Now Interviewer could ask more on this Like.
Its much easier implementation using Predicate interface where in you design for two conditions and pass on those conditions to the completable Future. The only difference is that now the logic of checking odd and even is controlled by the predicate itself.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
public class EvenOddPrinterWithPredicate {
private static final int MAX_NUMBER = 20;
private static AtomicInteger current = new AtomicInteger(1);
private static final Object lock = new Object();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Predicate<Integer> isEven = num -> num % 2 == 0;
Predicate<Integer> isOdd = isEven.negate();
CompletableFuture<Void> evenTask = CompletableFuture.runAsync(() -> printNumbers(isEven), executor);
CompletableFuture<Void> oddTask = CompletableFuture.runAsync(() -> printNumbers(isOdd), executor);
CompletableFuture.allOf(evenTask, oddTask).join(); // Wait for both tasks to complete
executor.shutdown();
}
private static void printNumbers(Predicate<Integer> condition) {
while (current.get() <= MAX_NUMBER) {
synchronized (lock) {
if (condition.test(current.get())) {
System.out.println((condition == isEven ? "Even: " : "Odd: ") + current.getAndIncrement());
lock.notify();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
}
Key Changes and Features:
In all the examples that we see we have not tried to bench mark the performance however we have tried to solve the same problem in multiple ways in order to exude the depth of the topic.
The key take aways: