归结为一个线程通过某种服务提交作业。作业在某些TPExecutor中执行。之后,此服务检查结果并在特定条件下在原始线程中抛出异常(作业超过最大重试次数等)。下面的代码片段大致说明了遗留代码中的这种情况:
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-before不是强制的(例如'countDown'发生在'setThrowable'之前)并且程序继续工作(但应该失败)。 我很感激有关如何解决此案件的任何帮助。 约束条件是:一周内发布,对现有代码库的影响最小。
答案 0 :(得分:6)
上面的代码(如现在更新的)应该按预期工作,而不使用进一步的同步机制。使用CountDownLatch
await()
和countdown()
方法强制执行内存屏障及其相应的'发生之前'关系。
来自API docs:
在“释放”同步器方法之前的操作,例如Lock.unlock,Semaphore.release和CountDownLatch.countDown,在成功的“获取”方法(如Lock.lock,Semaphore.acquire)之后的操作之前发生,另一个线程中同一个同步器对象上的Condition.await和CountDownLatch.await。
如果您定期处理并发问题,请获取'Java Concurrency in Practice'的副本,这是Java并发圣经,并且在您的书架上非常值得: - )。
答案 1 :(得分:2)
我怀疑你需要
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");
}
}
在FutureTask的代码中,有一条评论。
/**
* The thread running task. When nulled after set/cancel, this
* indicates that the results are accessible. Must be
* volatile, to ensure visibility upon completion.
*/
如果你不打算重新使用JDK中的代码,它仍然值得一读,这样你就可以了解它们使用的任何技巧。