I am trying to map some business case to usage of Cyclic Barriers. Let's say we have a promotional offer going on and only 3 customers can get the promotional offer. The rest will not get the offer.
To map this scenario, I have made use of Cyclic Barrier. Even though the code is working, I am not sure how to handle the scenario, where some of the customers, will not be able to get the offer. Right now, I tried to use await() API with a timeout value, so that I can catch TimeoutException and let the customer know, that he could not avail the promotional offer. This led to BarrierBrokenException for another waiting thread.
I would like to know, how can we gracefully handle these scenarios such that the selected customers avail promotional offer, while those who could not follow a different code path.
My code -
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
Thread[] threads = new Thread[5];
CyclicBarrier barrier = new CyclicBarrier(3, ()->{System.out.println("Barrier limit of 3 reached. 3 threads will get the promotional offer!");});
Runnable nr = new PromotionRunnable(barrier);
int i = 0;
for (Thread t : threads) {
t = new Thread(nr, "Thread " + ++i);
t.start();
}
System.out.println("main thread has completed");
}
private static class PromotionRunnable implements Runnable {
private final CyclicBarrier barrier;
public PromotionRunnable(final CyclicBarrier barrier) {
this.barrier = barrier;
}
/*
* As per the doc, BrokenBarrierException is thrown when another thread timed out while the current thread was waiting.
* This explains why we are able to see both Timeout and Broken Barrier Exceptions.
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " trying to get the promotional offer!");
try {
barrier.await(2000L, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
return;
} catch (BrokenBarrierException e) {
System.out.println(Thread.currentThread().getName() + " could not get the promotional offer, due to barrier exception");
return;
} catch (TimeoutException e) {
System.out.println(Thread.currentThread().getName() + " could not get the promotional offer, due to timeout exception");
return;
}
System.out.println(Thread.currentThread().getName() + " got the promotional offer!");
}
}
}
Output from one of the runs -
A CyclicBarrier
will only trip when 3 customers are trying to access the offer.
Therefore if only 1 customer is trying to access it, it will block until 2 other customers try to access it too! Once the barrier trips, it is simply reset and the mecanism just starts over. You can observe if create 6+ threads instead of 5.
So CyclicBarrier
does not seem to be what you are looking for.
You probably want to count the number of customers who already accessed the offer and refuse it to new customers:
private static class PromotionBarrier {
private final AtomicBoolean hasAccess = new AtomicBoolean(false);
private final AtomicLong counter = new AtomicLong(0);
private final long maxCustomers = 3;
public boolean hasAccess() {
if(hasAccess.get()) {
long value = counter.incrementAndGet();
if(value <= maxCustomers) {
return true;
} else {
hasAccess.set(false);
return false;
}
}
return false;
}
}
private static class PromotionRunnable implements Runnable {
private final PromotionBarrier promotionBarrier;
public PromotionRunnable(final PromotionBarrier promotionBarrier) {
this.promotionBarrier = barrier;
}
@Override
public void run() {
if(promotionBarrier.hasAccess()) {
// Yoohoo I got it!
} else {
// Rha I am too late!!
}
}