【问题标题】:Better ways to handle exceptions related to Cyclic Barriers处理与循环障碍相关的异常的更好方法
【发布时间】:2015-11-21 23:01:04
【问题描述】:

我正在尝试将一些业务案例映射到循环障碍的使用。假设我们正在进行促销优惠,只有 3 位客户可以获得促销优惠。其余的将无法获得报价。

为了映射这个场景,我使用了 Cyclic Barrier。即使代码正常工作,我也不确定如何处理某些客户无法获得报价的情况。现在,我尝试使用具有超时值的 await() API,以便我可以捕获 TimeoutException 并让客户知道他无法使用促销优惠。这导致另一个等待线程出现 BarrierBrokenException

我想知道,我们如何才能优雅地处理这些场景,以便选定的客户获得促销优惠,而那些无法遵循不同代码路径的客户。

我的代码 -

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!");
    }
 }
}

其中一次运行的输出 -

  • 线程 1 试图获得促销优惠!
  • 线程 4 试图获得促销优惠!
  • 主线程已完成
  • 线程 3 试图获得促销优惠!
  • 线程 2 试图获得促销优惠!
  • 线程 5 试图获得促销优惠!
  • 障碍达到前三名,他们将获得促销优惠!
  • 线程 2 获得促销优惠!
  • 线程 1 获得促销优惠!
  • 线程 5 收到促销优惠!
  • 线程 3 无法获得促销优惠,由于超时异常
  • 由于屏障异常,线程 4 无法获得促销优惠

【问题讨论】:

    标签: java multithreading concurrency java.util.concurrent cyclicbarrier


    【解决方案1】:

    CyclicBarrier 用于当您有多个线程,并且您希望它们都同时开始做某事时。如果为 N 个线程设置了屏障,那么它会让前 N-1 个线程等待,直到第 N 个线程到达,然后再让它们全部离开。

    这可能不是你想要的。您希望前 3 个线程获得奖励,其余线程空手而归。 CyclicBarrier 就是让线程等待某些东西,但你不希望你的线程等待。

    Semaphore 也是为了让线程等待某些东西。

    我喜欢@OldCurmudgeon 关于使用 AtomicInteger 的建议。

    AtomicInteger设置为等于奖品数量,然后让每个线程调用ai.decrementAndGet()。如果结果 >= 0,则线程可以领取奖品。如果结果是

    【讨论】:

      【解决方案2】:

      CyclicBarrier 只会在 3 位客户尝试访问该优惠时触发。

      因此,如果只有 1 位客户尝试访问它,它将被阻止,直到其他 2 位客户也尝试访问它!一旦屏障跳闸,它就会被简单地重置,并且机制刚刚开始。您可以观察是否创建了 6 个以上的线程而不是 5 个。

      所以CyclicBarrier 似乎不是您想要的。

      您可能想统计已经获得该优惠的客户数量,并拒绝新客户:

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

      【讨论】:

        【解决方案3】:

        您错误地使用了CyclicBarrier。这是为了让多个线程同步。

        一种同步辅助工具,它允许一组线程相互等待以达到一个共同的障碍点。 CyclicBarriers 在涉及固定大小的线程组的程序中很有用,这些线程组必须偶尔相互等待。屏障被称为循环的,因为它可以在等待线程被释放后重新使用。

        您可能需要使用Semaphore,尽管我不确定您需要什么。

        计数信号量。从概念上讲,信号量维护一组许可。如有必要,每个 acquire() 都会阻塞,直到获得许可,然后再接受它。每个 release() 添加一个许可,可能会释放一个阻塞的获取者。但是,没有使用实际的许可对象; Semaphore 只是对可用数量进行计数并采取相应措施。

        您可能正在寻找AtomicInteger

        可以自动更新的 int 值。有关原子变量属性的描述,请参阅 java.util.concurrent.atomic 包规范。 AtomicInteger 用于原子递增计数器等应用程序,不能用作 Integer 的替代品。

        【讨论】:

        • 是的,通读 cmets 后,CyclicBarrier 可能不是用于我假设场景的正确构造。你能告诉我使用循环障碍的实际场景吗?我也在考虑用计数信号量实现相同的功能,但不确定用例是否对信号量有效。
        • @user3842182 CyclicBarriers 在涉及固定大小的线程组的程序中很有用,这些线程组必须偶尔相互等待。 - 不能说太多了。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-12
        • 1970-01-01
        • 1970-01-01
        • 2014-09-07
        • 2015-09-25
        • 2020-01-08
        相关资源
        最近更新 更多