如何“取消”CountDownLatch?

时间:2012-05-04 18:02:00

标签: java concurrency java.util.concurrent

我有多个消费者线程使用CountDownLatch等待await()大小为1的countDown()。我有一个生成器线程在成功完成时调用abortCountDown()

当没有错误时,这很有用。

但是,如果生产者检测到错误,我希望它能够向用户线程发出错误信号。理想情况下,我可以让生产者调用像countDown()这样的东西,让所有的消费者都收到InterruptedException或其他一些异常。我不想调用await(),因为这需要我的所有消费者线程在调用CountDownLatch之后再进行一次手动检查以获得成功。我宁愿他们只收到一个他们已经知道如何处理的例外。

我知道CountDownLatch中没有中止设施。是否有另一个同步原语,我可以很容易地适应,以有效地创建支持中止倒计时的{{1}}?

5 个答案:

答案 0 :(得分:15)

JB Nizet有一个很好的答案。我把它拿了一下并擦了一下。结果是一个名为AbortableCountDownLatch的CountDownLatch的子类,它向类中添加一个“abort()”方法,该方法将导致等待锁存器的所有线程接收到AbortException(InterruptedException的子类)。

此外,与JB的类不同,AbortableCountDownLatch将在中止时立即中止所有阻塞线程,而不是等待倒计时达到零(对于使用计数> 1的情况)。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class AbortableCountDownLatch extends CountDownLatch {
    protected boolean aborted = false;

    public AbortableCountDownLatch(int count) {
        super(count);
    }


   /**
     * Unblocks all threads waiting on this latch and cause them to receive an
     * AbortedException.  If the latch has already counted all the way down,
     * this method does nothing.
     */
    public void abort() {
        if( getCount()==0 )
            return;

        this.aborted = true;
        while(getCount()>0)
            countDown();
    }


    @Override
    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        final boolean rtrn = super.await(timeout,unit);
        if (aborted)
            throw new AbortedException();
        return rtrn;
    }

    @Override
    public void await() throws InterruptedException {
        super.await();
        if (aborted)
            throw new AbortedException();
    }


    public static class AbortedException extends InterruptedException {
        public AbortedException() {
        }

        public AbortedException(String detailMessage) {
            super(detailMessage);
        }
    }
}

答案 1 :(得分:13)

在内部使用CountDownLatch将此行为封装在特定的更高级别的类中:

public class MyLatch {
    private CountDownLatch latch;
    private boolean aborted;
    ...

    // called by consumers
    public void await() throws AbortedException {
        latch.await();
        if (aborted) {
            throw new AbortedException();
        }
    }

    // called by producer
    public void abort() {
        this.aborted = true;
        latch.countDown();
    }

    // called by producer
    public void succeed() {
        latch.countDown();
    }
}

答案 2 :(得分:4)

您可以在CountDownLatch周围创建一个包装,提供取消服务员的功能。它需要跟踪等待的线程并在它们超时时释放它们,并记住锁存器被取消,以便将来对await的调用会立即中断。

public class CancellableCountDownLatch
{
    final CountDownLatch latch;
    final List<Thread> waiters;
    boolean cancelled = false;

    public CancellableCountDownLatch(int count) {
        latch = new CountDownLatch(count);
        waiters = new ArrayList<Thread>();
    }

    public void await() throws InterruptedException {
        try {
            addWaiter();
            latch.await();
        }
        finally {
            removeWaiter();
        }
    }

    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        try {
            addWaiter();
            return latch.await(timeout, unit);
        }
        finally {
            removeWaiter();
        }
    }

    private synchronized void addWaiter() throws InterruptedException {
        if (cancelled) {
            Thread.currentThread().interrupt();
            throw new InterruptedException("Latch has already been cancelled");
        }
        waiters.add(Thread.currentThread());
    }

    private synchronized void removeWaiter() {
        waiters.remove(Thread.currentThread());
    }

    public void countDown() {
        latch.countDown();
    }

    public synchronized void cancel() {
        if (!cancelled) {
            cancelled = true;
            for (Thread waiter : waiters) {
                waiter.interrupt();
            }
            waiters.clear();
        }
    }

    public long getCount() {
        return latch.getCount();
    }

    @Override
    public String toString() {
        return latch.toString();
    }
}

答案 3 :(得分:0)

您可以使用允许访问受保护的getWaitingThreads方法的CountDownLatch推出自己的ReentrantLock

示例:

public class FailableCountDownLatch {
    private static class ConditionReentrantLock extends ReentrantLock {
        private static final long serialVersionUID = 2974195457854549498L;

        @Override
        public Collection<Thread> getWaitingThreads(Condition c) {
            return super.getWaitingThreads(c);
        }
    }

    private final ConditionReentrantLock lock = new ConditionReentrantLock();
    private final Condition countIsZero = lock.newCondition();
    private long count;

    public FailableCountDownLatch(long count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        lock.lock();
        try {
            if (getCount() > 0) {
                countIsZero.await();
            }
        } finally {
            lock.unlock();
        }
    }

    public boolean await(long time, TimeUnit unit) throws InterruptedException {
        lock.lock();
        try {
            if (getCount() > 0) {
                return countIsZero.await(time, unit);
            }
        } finally {
            lock.unlock();
        }
        return true;
    }

    public long getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }

    public void countDown() {
        lock.lock();
        try {
            if (count > 0) {
                count--;

                if (count == 0) {
                    countIsZero.signalAll();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    public void abortCountDown() {
        lock.lock();
        try {
            for (Thread t : lock.getWaitingThreads(countIsZero)) {
                t.interrupt();
            }
        } finally {
            lock.unlock();
        }
    }
}

您可能希望更改此类,以便在取消后对InterruptedException的新号召唤await。如果您需要该功能,甚至可以让此类扩展CountDownLatch

答案 4 :(得分:0)

从 Java 8 开始,您可以为此使用 CompletableFuture。一个或多个线程可以调用阻塞 get() 方法:

CompletableFuture<Void> cf = new CompletableFuture<>();
try {
  cf.get();
} catch (ExecutionException e) {
  //act on error
}

另一个线程可以使用 cf.complete(null) 成功完成,或者使用 cf.completeExceptionally(new MyException()) 异常完成