在Java中阻塞并同时释放可变数量的线程

时间:2012-07-07 14:10:09

标签: java concurrency

我正在使用Java构建一个缓冲区,它将收集许多线程请求的写入操作,并将它们作为一组刷新,例如每秒一次。我想给它一个名为waitForFlush的方法,它将阻塞并调用它,直到下一个flush事件完成。同时,一个独立的独立线程正在刷新并在一个循环中睡觉。所以我基本上寻找一个并发结构或模式,允许许多线程在特定点阻塞,然后同时释放它们,我发现Java的内置并发原语都不是真的非常接近。到目前为止,我提出的最好的是wait / notifyAll,如下所示:

public class Buffer {

  private volatile long lastFlushTime = System.currentTimeMillis();
  private final Object flushMonitor = new Object();

  public void waitForFlush() {
    long entryTime = System.currentTimeMillis();

    synchronized(flushMonitor) {
      while(lastFlushTime <= entryTime) {
        flushMonitor.wait();
      }
    }
  }

  public void flush() {
    // do flush stuff here
    synchronized(flushMonitor) {
      lastFlushTime = System.currentTimeMillis();
      flushMonitor.notifyAll();
    }
  }
}

虽然我认为这在实践中会很好用,waitForNotify()中的同步块对我来说仍然有些不完美。理想情况下,对于此用例,您可以调用wait()而无需在关联的对象上进行同步,并且所有被阻止的线程将在调用notifyAll()的同一时刻释放,而不是必须一个一个地退出同步块。

所以,总的来说,是否有更好的方法来阻止并同时释放可变数量的线程(我认为,Semaphore和CountDownLatch类只适用于固定数量的线程)?

2 个答案:

答案 0 :(得分:3)

虽然CountDownLatch可以用于Marko Topolnik和munyengm提到的一次性案例。它在循环场景中失败(即每个CDL只能await&amp; countDown一次。然后你可以考虑一个CyclicBarrier,但是在你的情况下会失败,因为你需要知道正在使用的线程数。

如果您可以使用Java 7,我建议使用Phaser。您可以将单线程信号发送到许多等待线程并重用。

final Phaser phaser = new Phaser(1);//register one thread to arrive

 public void waitForFlush() {
    int phase = phaser.getPhase();
    phaser.awaitAdvance(phase);    
 }

  public void flush() {
      lastFlushTime = System.currentTimeMillis();
      phaser.arrive(); //signals all waiting threads on the current phase and will increment the phase by 1
  }

答案 1 :(得分:2)

如果您正确使用CountDownLatch,我认为它可以为您完成。方法是在锁存器上创建可变数量的线程await,并且刷新线程调用countDown。锁存器始终初始化为1.这就是它的工作原理:

public class FlushControl
{
  private volatile CountDownLatch latch = new CountDownLatch(1);

  public void awaitFlush() throws InterruptedException { latch.await(); }

  public void flush() {
    final CountDownLatch l = latch;
    latch = new CountDownLatch(1);
    l.countDown();
  }
}