Эквивалент Java для .Net AutoResetEvent?

Что мне следует использовать, чтобы получить семантику, эквивалентную AutoResetEvent в Яве? (См. этот вопрос для ManualResetEvent).


person ripper234    schedule 07.07.2009    source источник


Ответы (5)


Ответ @ user249654 выглядел многообещающим. Я добавил несколько модульных тестов, чтобы проверить это, и действительно, он работает, как ожидалось.

Я также добавил перегрузку waitOne, которая требует тайм-аута.

Код здесь на тот случай, если кто-то сочтет его полезным:

Модульный тест

import org.junit.Assert;
import org.junit.Test;

import static java.lang.System.currentTimeMillis;

/**
 * @author Drew Noakes http://drewnoakes.com
 */
public class AutoResetEventTest
{
    @Test
    public void synchronisesProperly() throws InterruptedException
    {
        final AutoResetEvent event1 = new AutoResetEvent(false);
        final AutoResetEvent event2 = new AutoResetEvent(false);
        final int loopCount = 10;
        final int sleepMillis = 50;

        Thread thread1 = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try {
                    for (int i = 0; i < loopCount; i++)
                    {
                        long t = currentTimeMillis();
                        event1.waitOne();
                        Assert.assertTrue("Time to wait should be within 5ms of sleep time",
                                Math.abs(currentTimeMillis() - t - sleepMillis) < 5);
                        Thread.sleep(sleepMillis);
                        t = currentTimeMillis();
                        event2.set();
                        Assert.assertTrue("Time to set should be within 1ms", currentTimeMillis() - t <= 1);
                    }
                } catch (InterruptedException e) {
                    Assert.fail();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try {
                    for (int i = 0; i < loopCount; i++)
                    {
                        Thread.sleep(sleepMillis);
                        long t = currentTimeMillis();
                        event1.set();
                        Assert.assertTrue("Time to set should be within 1ms", currentTimeMillis() - t <= 1);
                        t = currentTimeMillis();
                        event2.waitOne();
                        Assert.assertTrue("Time to wait should be within 5ms of sleep time",
                                Math.abs(currentTimeMillis() - t - sleepMillis) < 5);
                    }
                } catch (InterruptedException e) {
                    Assert.fail();
                }
            }
        });

        long t = currentTimeMillis();

        thread1.start();
        thread2.start();

        int maxTimeMillis = loopCount * sleepMillis * 2 * 2;

        thread1.join(maxTimeMillis);
        thread2.join(maxTimeMillis);

        Assert.assertTrue("Thread should not be blocked.", currentTimeMillis() - t < maxTimeMillis);
    }

    @Test
    public void timeout() throws InterruptedException
    {
        AutoResetEvent event = new AutoResetEvent(false);

        int timeoutMillis = 100;
        long t = currentTimeMillis();
        event.waitOne(timeoutMillis);
        long took = currentTimeMillis() - t;
        Assert.assertTrue("Timeout should have occurred, taking within 5ms of the timeout period, but took " + took,
                Math.abs(took - timeoutMillis) < 5);
    }

    @Test
    public void noBlockIfInitiallyOpen() throws InterruptedException
    {
        AutoResetEvent event = new AutoResetEvent(true);

        long t = currentTimeMillis();
        event.waitOne(200);
        Assert.assertTrue("Should not have taken very long to wait when already open",
                Math.abs(currentTimeMillis() - t) < 5);
    }
}

AutoResetEvent с перегрузкой, которая принимает тайм-аут

public class AutoResetEvent
{
    private final Object _monitor = new Object();
    private volatile boolean _isOpen = false;

    public AutoResetEvent(boolean open)
    {
        _isOpen = open;
    }

    public void waitOne() throws InterruptedException
    {
        synchronized (_monitor) {
            while (!_isOpen) {
                _monitor.wait();
            }
            _isOpen = false;
        }
    }

    public void waitOne(long timeout) throws InterruptedException
    {
        synchronized (_monitor) {
            long t = System.currentTimeMillis();
            while (!_isOpen) {
                _monitor.wait(timeout);
                // Check for timeout
                if (System.currentTimeMillis() - t >= timeout)
                    break;
            }
            _isOpen = false;
        }
    }

    public void set()
    {
        synchronized (_monitor) {
            _isOpen = true;
            _monitor.notify();
        }
    }

    public void reset()
    {
        _isOpen = false;
    }
}
person Drew Noakes    schedule 12.12.2012
comment
Привет, Дрю Ноукс, разве функция waitOne () не должна возвращать логическое значение, как в версии для C #? - person Masterminder; 29.08.2014
comment
Мне нравится ваш ответ, но нужно ли вам создать логическое значение WaitOne (), чтобы имитировать ответ по этой ссылке: msdn.microsoft.com/en-us/library/58195swd%28v=vs.110%29.aspx? Я предполагаю, что он вернет имеющуюся у вас переменную _isOpen, но основная идея заключается в том, что он будет блокировать текущий поток до тех пор, пока не будет получен сигнал. Что вы думаете? - person Masterminder; 29.08.2014
comment
@Masterminder, ты прав. Логическое возвращаемое значение требуется только в WaitOne (длительный тайм-аут), потому что, когда истекает тайм-аут, атрибут _isOpen может по-прежнему иметь значение false. - person Arnold Pistorius; 24.11.2015
comment
Разве метод set не должен вызывать _monitor.notifyAll() в случае, если есть несколько потоков, ожидающих метода waitOne()? См. Также: stackoverflow.com/a/2055584/1556915 - person lunatix; 25.04.2017

Мне удалось заставить CyclicBarrier работать для моих целей.

Вот код C #, который я пытался воспроизвести на Java (это просто демонстрационная программа, которую я написал, чтобы изолировать парадигму, теперь я использую ее в программах на C #, которые я пишу для генерации видео в реальном времени, чтобы обеспечить точный контроль частоты кадров) :

using System;
using System.Timers;
using System.Threading;

namespace TimerTest
{
    class Program
    {
        static AutoResetEvent are = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            System.Timers.Timer t = new System.Timers.Timer(1000);
            t.Elapsed += new ElapsedEventHandler(delegate { are.Set(); });
            t.Enabled = true;
            while (true)
            {
                are.WaitOne();
                Console.WriteLine("main");
            }
        }
    }
}

и вот код Java, который я придумал, чтобы сделать то же самое (используя класс CyclicBarrier, как предлагалось в предыдущем ответе):

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CyclicBarrier;

public class TimerTest2 {
    static CyclicBarrier cb;

    static class MyTimerTask extends TimerTask {
        private CyclicBarrier cb;
        public MyTimerTask(CyclicBarrier c) { cb = c; }

        public void run() { 
            try { cb.await(); } 
            catch (Exception e) { } 
        }
    }

    public static void main(String[] args) {
        cb = new CyclicBarrier(2);
        Timer t = new Timer();
        t.schedule(new MyTimerTask(cb), 1000, 1000);

        while (true) {
            try { cb.await(); } 
            catch (Exception e) { }
            System.out.println("main");
        }
    }
}
person bobkart    schedule 26.07.2009

Еще одно расширение решения из принятого ответа на случай, если вы хотите знать, закончилось ли ваше ожидание с таймаутом или с установленным событием (что и делает .NET AutoResetEvent).

public boolean waitOne(long timeout) throws InterruptedException {
    synchronized (monitor) {
        try {
            long t = System.currentTimeMillis();
            while (!isOpen) {
                monitor.wait(timeout);
                // Check for timeout
                if (System.currentTimeMillis() - t >= timeout)
                    break;
            }

            return isOpen;
        }
        finally {
            isOpen = false;
        }
    }
}
person user1921819    schedule 09.03.2015

Я считаю, что вы ищете либо CyclicBarrier, либо CountDownLatch.

person tddmonkey    schedule 07.07.2009
comment
CountDownLatch немного помог, но это не совсем то же самое. Заходите на StackOverflow - это невозможно, еще никто не реализовал ... - person ripper234; 07.07.2009

person    schedule
comment
Это выглядит многообещающе, но означает ли это, что один поток не может вызвать set событие, если другой ожидает? Ожидающий поток блокирует monitor, верно? - person Drew Noakes; 11.12.2012
comment
Похоже, я был неправ, и блокировка снимается во время ожидания. См. Аналогичные комментарии к этому ответу. - person Drew Noakes; 11.12.2012