Java 使用CountDownLatch时如何正确同步/锁定
它归结为一个线程通过某种服务提交作业。作业在某些TPExecutor中执行。之后,该服务检查结果,并在某些情况下(作业超过最大重试次数等)在原始线程中引发异常。下面的代码片段大致说明了遗留代码中的此场景:Java 使用CountDownLatch时如何正确同步/锁定,java,multithreading,locking,blocking,Java,Multithreading,Locking,Blocking,它归结为一个线程通过某种服务提交作业。作业在某些TPExecutor中执行。之后,该服务检查结果,并在某些情况下(作业超过最大重试次数等)在原始线程中引发异常。下面的代码片段大致说明了遗留代码中的此场景: import java.util.concurrent.CountDownLatch; public class IncorrectLockingExample { private static class Request { private final CountDownLat
import java.util.concurrent.CountDownLatch;
public class IncorrectLockingExample {
private static class Request {
private final CountDownLatch latch = new CountDownLatch(1);
private Throwable throwable;
public void await() {
try {
latch.await();
} catch (InterruptedException ignoredForDemoPurposes) {
}
}
public void countDown() {
latch.countDown();
}
public Throwable getThrowable() {
return throwable;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
}
private static final Request wrapper = new Request();
public static void main(String[] args) throws InterruptedException {
final Thread blockedThread = new Thread() {
public void run() {
wrapper.await();
synchronized (wrapper) {
if (wrapper.getThrowable() != null)
throw new RuntimeException(wrapper.getThrowable());
}
}
};
final Thread workingThread = new Thread() {
public void run() {
wrapper.setThrowable(new RuntimeException());
wrapper.countDown();
}
};
blockedThread.start();
workingThread.start();
blockedThread.join();
workingThread.join();
}
}
有时,(在我的机器上不可复制,但发生在16核服务器机器上)异常不会报告给原始线程。我认为这是因为before不是强制的(例如,“倒计时”发生在“setThrowable”之前),程序继续工作(但应该失败)。
如果您能为我解决这个案子提供帮助,我将不胜感激。
限制条件是:在一周内发布,对现有代码库的影响最小。我想您需要
private volatile Throwable throwable
您是否尝试过使用内置的ExecutorService,并为您这样做。下面的照片
future1 := result
future2 threw java.lang.IllegalStateException
future3 timed out
代码是
public static void main(String... args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future1 = executor.submit(new Callable<String>() {
public String call() throws Exception {
return "result";
}
});
Future<String> future2 = executor.submit(new Callable<String>() {
public String call() throws Exception {
throw new IllegalStateException();
}
});
Future<String> future3 = executor.submit(new Callable<String>() {
public String call() throws Exception {
Thread.sleep(2000);
throw new AssertionError();
}
});
printResult("future1", future1);
printResult("future2", future2);
printResult("future3", future3);
executor.shutdown();
}
private static void printResult(String description, Future<String> future) {
try {
System.out.println(description+" := "+future.get(1, TimeUnit.SECONDS));
} catch (InterruptedException e) {
System.out.println(description+" interrupted");
} catch (ExecutionException e) {
System.out.println(description+" threw "+e.getCause());
} catch (TimeoutException e) {
System.out.println(description+" timed out");
}
}
如果您不打算在JDK中重复使用代码,那么仍然值得一读,这样您就可以了解他们使用的任何技巧。上述代码(现在已更新)在不使用进一步的同步机制的情况下应能按预期工作。通过使用CountDownLatch
await()
和countdown()
方法强制执行内存屏障及其相应的“发生在”关系
从:
“释放”同步器方法(如Lock.unlock、Semaphore.release和CountDownLatch.countDown)之前的操作发生在另一个线程中的同一同步器对象上成功“获取”方法(如Lock.Lock、Semaphore.acquire、Condition.await和CountDownLatch.await)之后的操作之前
如果您定期处理并发问题,请为自己准备一份,这是Java并发圣经,它在您的书架上很值得一看:-)。250 KLOC项目在这里完全多线程,在16核上工作等。我们经常使用“高级”多线程功能,如倒计时闩锁。我们使用诸如Object的wait()方法和Thread的join()方法之类的低级方法的次数?零。在我看来,现在在默认API中有足够的高级并发功能,您不需要根据Java特性重新发明任何损坏的轮子+1对Peter Lawrey的回答。@Webinator:OP正在使用“高级”
CountDownLatch
功能用于此处的设计目的之一。您确定上面的代码没有按预期运行吗?在您进行更正后,我认为没有理由不这样做。我不知道wait()是ExecutorService
建议的一个坏轮子/+1,但是volatile
关键字在这里没有任何用处。感谢您提供有关FutureTask的示例。看起来这就是我们将来必须升级到的(而不是使用CountDownLatch和throwable的组合)。不幸的是,在保证之前,我的情况略有不同。如果FutureTask需要“volatile”运行程序,因为callable(innerRunAndReset方法)不能保证之前发生。CountDownLatch等待/倒计时保证在之前发生(感谢您指向API文档,我不知道这一事实)。但是,“先发制人”本身并不能保证在CountDownLatch.countDown之前所做更改的可见性。因此,唯一的解决方案是将throwable设置为volatile,或者在“synchronized(throwable){…}”块中进行更新。这是否意味着sence@PetroSemeniuk:不,before关系保证了跨越内存屏障的所有以前写入的可见性,即,在workingRead
beforecountDown()
中执行的任何操作都将在相应的wait()
返回后在blockedThread
中可见。仅为了创建内存屏障而使用synchronized
被认为是不好的(这就是volatile
的用途),无论如何,这里不需要volatile
,因为使用CountDownLatch
方法的线程同步已经创建了所需的内存屏障,并且发生在关系之前。
/**
* The thread running task. When nulled after set/cancel, this
* indicates that the results are accessible. Must be
* volatile, to ensure visibility upon completion.
*/