Java中的ManualResetEvent相当于什么?

34

为什么不看一下其他最佳答案呢? - Bogdan Mart
5个回答

28
class ManualResetEvent {

  private final Object monitor = new Object();
  private volatile boolean open = false;

  public ManualResetEvent(boolean open) {
    this.open = open;
  }

  public void waitOne() throws InterruptedException {
    synchronized (monitor) {
      while (open==false) {
          monitor.wait();
      }
    }
  }

  public boolean waitOne(long milliseconds) throws InterruptedException {
    synchronized (monitor) {
      if (open) 
        return true;
      monitor.wait(milliseconds);
        return open;
    }
  }

  public void set() {//open start
    synchronized (monitor) {
      open = true;
      monitor.notifyAll();
    }
  }

  public void reset() {//close stop
    open = false;
  }
}

看起来这根本行不通:线程一调用waitOne()并在monitor.wait()上阻塞。线程二调用set并在synchronized(monitor)上阻塞。这是唯一的使用情况,对吧?如果您只是省略set中的synchronized语句,我认为这没问题。 - lmat - Reinstate Monica
4
在Java中,如果要等待一个对象,必须在同步块中执行。在等待期间,该对象的监视器锁会被释放,因此可以在另一个线程中获取它。请参阅http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html。 - Wayne Uroda
@WayneUroda 对不起,我猜我不该问你为什么必须在synchronized块中才能通过调用wait释放锁。我相信这是老掉牙的问题;)。 - lmat - Reinstate Monica
在其他编程语言中,您可以在类似的实体上等待/通知,而无需将它们锁定(如C#、win32/GDI等)。我认为Java这样做是为了帮助程序员避免一些常见的竞态条件。也许这篇文章可以比我更好地解释:https://dev59.com/hHE85IYBdhLWcg3wbS_1 - Wayne Uroda
1
它工作得很好!并且它是在Java中正确实现ManualResetEvent。所有其他实现都不好或复杂。 - terentev

20
我所知道的最接近的是Semaphore。只需将其使用为“许可”计数为1,并且获取/释放操作基本上与您从ManualResetEvent所了解的相同。

将初始化为1的信号量,并且仅在最多有一个许可证可用的情况下使用,可以用作互斥锁。这通常被称为二进制信号量,因为它只有两个状态:一个许可证可用或零个许可证可用。当以这种方式使用时,二进制信号量具有一个特性(与许多锁实现不同),即“锁”可以由所有者之外的线程释放(因为信号量没有所有权的概念)。这在某些特定的上下文中可能很有用,例如死锁恢复。


我认为如果一个线程不能释放它自己没有先前获取的信号量,那么这个程序就无法正常工作。 - ripper234
1
我收回之前的说法 - 根据文档,似乎可以在不获取信号量的情况下释放它。 - ripper234
3
没错,这是因为信号量没有所有权概念。它们基本上是同步计数器,如果计数器为0,则让线程等待。 - Lucero
1
如果信号量在没有正确获取的情况下多次释放,则availablePermits将大于1,并且它的行为不同于ManualResetEvent,因为reset()不会导致waitOne()等待。 - Yuan
一旦您Set了一个ManualResetEvent,它将永久打开,所有当前和未来的Wait都将通过。Semaphore一旦被acquire以允许一个线程通过,就会停止所有其他线程。最接近的方法是在每个acquire之后立即调用release,但这仍然会产生大量的开销和微阻塞。 - Agent_L
1
@Agent_L 当然,你是正确的,它需要一个立即的“release”才能模拟“ManualResetEvent”,否则你将得到一个“AutoResetEvent”。就像我所写的那样,这只是我在Java中了解到的最接近同步原语。 “ManualResetEvent”的常见用途是让一个线程等待同步,这种用法不会受到微块(仅当多个线程同时等待事件时才会发生)和开销应该相当小(与另一个答案中提供的监视器解决方案相当)。 - Lucero

10

尝试使用计数为一的CountDownLatch

CountDownLatch startSignal = new CountDownLatch(1);

11
CountDownLatch 的问题在于它不可重用。一旦计数器减至0,就无法再使用它。虽然可以用一个新的CountDownLatch实例来替换它,但如果不正确使用可能会导致竞态条件。 - ripper234

3

基于:

ManualResetEvent 允许线程通过信号进行通信。通常,这种通信涉及一个任务,其中一个线程必须在其他线程可以继续之前完成。

来自于此处:

http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx

你可能想要查看Java并发包中的障碍物 - 特别是我认为的CyclicBarrier

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/CyclicBarrier.html

它会阻止一定数量的线程,直到特定事件发生。所有线程必须在屏障点汇聚。


你能提供一些Barrier的例子吗?我在文档中没有看到例子。 - Bogdan Mart
哦,我找到了一篇文章:http://tutorials.jenkov.com/java-util-concurrent/cyclicbarrier.html因此,它会阻塞直到所有线程都到达屏障(形成临界质量),然后才会继续。非常有趣。现在我知道这个东西了。不幸的是,它对问题并不是很有用,但是每个Java对象都有wait,这是ManualResetEvent。 - Bogdan Mart
@BogdanMart 但它会自动重新阻塞,这意味着它既不是ManualResetEvent也不是AutoResetEvent - Agent_L
@BogdanMart 我不是在谈论其他答案,我是在谈论你的评论不正确并误导了其他读者。 - Agent_L

2

我认为.NET MRE的关键在于线程亲和性及其能够在调用Set时让所有等待线程通过。我发现Semaphore的使用效果很好。然而,如果我有10或15个等待的线程,那么我就会遇到另一个问题。具体来说,它发生在调用Set时。在.Net中,所有等待线程都被释放。使用信号量并不能全部释放。因此,我将其封装在一个类中。注意:我非常熟悉.NET线程。我相对较新于Java线程和同步。尽管如此,我愿意跳进去得到一些真正的反馈。下面是我的实现,假设一个Java新手会这样做:

public class ManualEvent {
private final static int MAX_WAIT = 1000;
private final static String TAG = "ManualEvent"; 
private Semaphore semaphore = new Semaphore(MAX_WAIT, false);

private volatile boolean signaled = false;
public ManualEvent(boolean signaled) {
    this.signaled = signaled; 
    if (!signaled) {
        semaphore.drainPermits();
    }
}

public boolean WaitOne() {
    return WaitOne(Long.MAX_VALUE);
}

private volatile int count = 0;
public boolean WaitOne(long millis) {
    boolean bRc = true;
    if (signaled)
        return true;

    try {
        ++count;
        if (count > MAX_WAIT) {
            Log.w(TAG, "More requests than waits: " + String.valueOf(count));
        }

        Log.d(TAG, "ManualEvent WaitOne Entered");
        bRc = semaphore.tryAcquire(millis, TimeUnit.MILLISECONDS);
        Log.d(TAG, "ManualEvent WaitOne=" + String.valueOf(bRc));
    }
    catch (InterruptedException e) {
        bRc = false;
    }
    finally {
        --count;
    }

    Log.d(TAG, "ManualEvent WaitOne Exit");
    return bRc;
}

public void Set() {
    Log.d(TAG, "ManualEvent Set");
    signaled = true;
    semaphore.release(MAX_WAIT);
}

public void Reset() {
    signaled = false;
    //stop any new requests
    int count = semaphore.drainPermits();
    Log.d(TAG, "ManualEvent Reset: Permits drained=" + String.valueOf(count));
}

另外需要注意的是,我基本上打赌任何时候都不会有超过1000个请求在等待释放。通过批量释放和获取,我试图释放任何等待的线程。请注意,调用WaitOne每次只工作1个许可证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接