繁体   English   中英

使用lambda保持线程安全

[英]Preserving thread safety using lambda

我正在尝试更新一些代码以使用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);

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

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM