我有多个消费者线程使用CountDownLatch
等待await()
大小为1的countDown()
。我有一个生成器线程在成功完成时调用abortCountDown()
。
当没有错误时,这很有用。
但是,如果生产者检测到错误,我希望它能够向用户线程发出错误信号。理想情况下,我可以让生产者调用像countDown()
这样的东西,让所有的消费者都收到InterruptedException或其他一些异常。我不想调用await()
,因为这需要我的所有消费者线程在调用CountDownLatch
之后再进行一次手动检查以获得成功。我宁愿他们只收到一个他们已经知道如何处理的例外。
我知道CountDownLatch
中没有中止设施。是否有另一个同步原语,我可以很容易地适应,以有效地创建支持中止倒计时的{{1}}?
答案 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())
异常完成