使用CountDownLatch时如何正确同步/锁定

时间:2010-12-04 09:21:23

标签: java multithreading locking blocking

归结为一个线程通过某种服务提交作业。作业在某些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'之前)并且程序继续工作(但应该失败)。 我很感激有关如何解决此案件的任何帮助。 约束条件是:一周内发布,对现有代码库的影响最小。

2 个答案:

答案 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中的代码,它仍然值得一读,这样你就可以了解它们使用的任何技巧。