Search code examples
javamultithreadingconcurrencyjava.util.concurrentcyclicbarrier

Better ways to handle exceptions related to Cyclic Barriers


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 -

  • Thread 1 trying to get the promotional offer!
  • Thread 4 trying to get the promotional offer!
  • main thread has completed
  • Thread 3 trying to get the promotional offer!
  • Thread 2 trying to get the promotional offer!
  • Thread 5 trying to get the promotional offer!
  • Barrier reached for top 3, they will get the promotional offer!
  • Thread 2 got the promotional offer!
  • Thread 1 got the promotional offer!
  • Thread 5 got the promotional offer!
  • Thread 3 could not get the promotional offer, due to timeout exception
  • Thread 4 could not get the promotional offer, due to barrier exception

Solution

  • 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!!
            }
        }