【问题标题】:What blocking object to use?使用什么阻塞对象?
【发布时间】:2013-10-15 21:30:53
【问题描述】:

我需要一个阻塞对象来触发一些事件。 (单个)消费者应该等待触发发生。然后它会做一些事情。然后它再次等待触发。 触发器由多个不同的线程(生产者)激活。但是生产者不产生任何数据。这种触发器的语义是:“消费者必须做某事”(例如重新计算一些值,因为底层数据发生了变化)。 这意味着即使触发器被多次激活,它也应该作为单个触发器出现在消费者面前。

我考虑过使用CountDownLatchArrayBlockingQueue,但它们似乎不合适。

这是我想使用的触发器构造:

public class Trigger{
  private final MagicBlockingObject blockingLatch;

  public void trigger(){
     //activate the blockingLatch, so that a call to waitForTrigger() returns
  } 

  public void waitForTrigger(){
    //read from the blockingLatch. This should block until trigger() is called.
  }
}

MagicBlockingObject 使用什么有任何想法?

BlockingQueue 似乎合适,但如果队列已满,我没有找到将其限制为单个内容对象而不阻塞生产者的方法。

【问题讨论】:

  • 我想你在找java.util.concurrent.locks.Condition
  • 在这种情况下同步到简单吗?
  • 您只需简单地使用 synchonize 和 wait()、notify() 调用即可。
  • “如果触发器被多次激活,它应该对消费者显示为单个触发器” - 这是非常危险的设计决策,因为某些激活被忽略了。您必须三思而后行触发器被消费者清除的方式/时间,因为它会影响哪些激活被忽略。
  • @AlexeiKaigorodov 我不这么认为。你在哪里看到问题?触发器不会被消费者手动清除,而只会被消费。在通知消费者之前,无论是 1 个还是 10 个生产者激活了触发器都没有关系。您在 Duncan Jones 解决方案中发现任何问题吗?

标签: java multithreading blocking


【解决方案1】:

您可以使用容量为 1 的 ArrayBlockingQueue 来解决此问题:

public class Trigger{
  private final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

  public void trigger(){
     queue.offer("foo");
  } 

  public void waitForTrigger(){
    queue.take();
  }
}

【讨论】:

  • 这似乎是最好的解决方案。我没想过使用offer()。由于offer() 不会阻止生产者,因此它可以像我想要的那样工作。
  • 您可以轻松避免创建新对象,例如Queue&lt;String&gt; + offer("foo") 或使用现有的对象,如 Boolean.TRUE(这就是 Collections.newSetFromMap() 所做的)
  • @zapl 好主意,我偷了你的字符串建议。
【解决方案2】:

这样的简单解决方案有什么问题:

public class Trigger {
    private final Object blockingLatch = new Object();

    public void trigger() {
        //activate the blockingLatch, so that a call to waitForTrigger() returns
        synchronized(blockingLatch){
            blockingLatch.notify();
        }
    }

    public void waitForTrigger() throws InterruptedException {
        //read from the blockingLatch. This should block until trigger() is called.
        synchronized(blockingLatch){
            blockingLatch.wait();
        }
    }
}

消费者将调用 waitForTrigger() 并阻塞直到生产者没有调用 trigger()。如果消费者没有被阻塞,那么生产者调用 trigger() 不会有任何影响。

【讨论】:

  • 当然,你是对的。我没有考虑简单的等待()/通知()。谢谢,有帮助。使用offer() 方法时,似乎即使是 ArrayBlockingQueue 也可以工作,因为它不会阻塞。
  • @radlan,这是你需要的吗()?
  • 啊!看来我对 wait()/notify() 的看法是不对的。如果 Producer 调用 trigger(),但 Consumer 还没有调用 waitForTrigger() 怎么办?
  • 那么什么都不会发生。简单的生产者将完成方法。但是如果消费者调用了blockingLatch.wait(),那么消费者会收到通知并解除阻塞。
  • 是的。那就是问题所在。 :-) 因此,Duncan Jones 的建议更适合我的情况。
【解决方案3】:

java.util.concurrent 有很多不错的实用程序。 waitnotify 应该被认为是过时的。

如果我了解您的问题,您可以尝试使用Semaphore

public class Blocking {
    private final Semaphore openTasks = new Semaphore(0);
    public void addTask() {
        // add 1
        openTasks.release();
    }
    public void takeAllTasks() throws InterruptedException {
        int immediately = openTasks.drainPermits();
        if (immediately > 0) {
            // there was a task, no need to wait
            return;
        }
        // wait for task
        openTasks.acquire();
        // ensure we leave this method without leaving permits
        openTasks.drainPermits()
    }
}

无限数量的生产者可以向信号量添加“许可”,而您的消费者只需将它们全部拿走或等待至少一个出现。

【讨论】:

  • 虽然您的答案似乎正确,但我接受了 Duncan Jones 的答案,因为它的代码更简单一些。不过也谢谢你。它也可以工作。
  • @radlan wait / notify 有一些问题。例如,当您的消费者当前不在waitForTrigger 中时,那段时间发生的每一个notify 都会变得糟糕,因为没有任何东西在等待他们。这可能是一个理想的属性,但它通常是一个问题。
  • @radlan 哎呀,忘了我说的,很好的解决方案。
  • 如果多个生产者在openTasks.acquire() 执行之前(但在drainPermits() 之后)调用了addTask(),那么takeAllTasks() 将在下一次调用时立即返回。我不确定这是否是理想的行为。
  • @DuncanJones 哦,对,那不好。最后添加另一个drainPermits 应该可以解决这个问题。
猜你喜欢
  • 2019-04-02
  • 2011-10-16
  • 2012-10-31
  • 1970-01-01
  • 2012-05-26
  • 1970-01-01
  • 2019-08-08
  • 1970-01-01
相关资源
最近更新 更多