使用lambda保持线程安全

时间:2014-11-04 20:24:54

标签: java multithreading lambda thread-safety

我正在尝试更新一些代码以使用lambda表达式,但我在保持线程安全方面遇到了一些麻烦。

我有多个线程正在运行,最终调用以下回调,该回调具有synchronized方法,可将某些结果添加到LinkedList

final List<Document> mappedDocs = new LinkedList<>();
final MapCallback<Integer, Document> mapCallback = new MapCallback<Integer, Document>() {
    @Override
    public synchronized void done(int file, List<Document> results) {
        mappedDocs.addAll(results);
    }
};

然而,当我将它转换为lambda表达式时,我丢失了synchronized关键字,我不完全确定如何将其恢复。每当我运行代码时,我现在都会收到NullPointerException。

final MapCallback<Integer, Document> mapCallback = (int file, List<Document> results) -> mappedDocs.addAll(results);

如何让这个线程再次安全?

2 个答案:

答案 0 :(得分:10)

您可以在不同的监视器上同步它,例如:

final MapCallback<Integer, Document> mapCallback = (int file, List<Document> results) -> {
  synchronized(mappedDocs) {
    mappedDocs.addAll(results);
  }
};

或者使用线程安全结构,例如CopyOnWriteArrayList或BlockingQueue。

答案 1 :(得分:3)

我强烈建议将mappedDocs设置为线程安全的数据结构(例如来自java.util.concurrent的数据结构)或者使用Collections.synchronizedList创建的同步包装器。

我认为你很幸运同步使用匿名内部类。这是有效的,因为它只有一个实例,并且没有其他代码可以改变mappedDocs

(实际上你可能有一个内存可见性问题,即使事情仍然存在。如果其他线程调用MapCallback来添加元素,那么在构建之后和读取之前需要在mappedDocs上同步其他内容。添加元素。)

问题的根源在于使用这种方式的匿名内部类似于一个函数,但是由于新对象的创建是显而易见的,所以很容易做一些事情,比如同步它。但这非常脆弱。如果这被重构以便创建多个AIC实例(例如,处理来自多个源的文档),或者如果创建了不同的AIC(例如,如果需要重新处理则从列表中删除文档),在单个AIC实例上进行同步会被彻底打破。

mappedDocs转换为线程安全的数据结构或包装器可以解决内存可见性问题和并发访问问题。这使您可以使用简单的lambda表单,它允许您在mappedDocs上引入新操作,而不考虑在其上运行的线程。