当前位置: 首页 > 面试题库 >

Java 8 Streams中产生副作用的危险是什么?

韶宏邈
2023-03-14
问题内容

我试图理解在“流文档”中发现的警告。我已经习惯了使用forEach()作为通用迭代器。这导致我编写这种类型的代码:

public class FooCache {
    private static Map<Integer, Integer> sortOrderCache = new ConcurrentHashMap<>();
    private static Map<Integer, String> codeNameCache = new ConcurrentHashMap<>();

    public static void populateCache() {
        List<Foo> myThings = getThings();

        myThings.forEach(thing -> {
            sortOrderCache.put(thing.getId(), thing.getSortOrder());
            codeNameCache.put(thing.getId(), thing.getCodeName())
        });
    }
}

这是一个简单的例子。我了解该代码违反了Oracle关于有状态lamda和副作用的警告。但是我不明白为什么这个警告存在。

运行此代码时,它似乎表现出预期的效果。那么,我如何打破这一点来说明为什么这是一个坏主意?

在某种程度上,我读到以下内容:

如果并行执行,则ArrayList的非线程安全性将导致错误的结果,而添加所需的同步将导致争用,从而损害了并行性的好处。

但是,谁能增加清晰度以帮助我理解警告?


问题答案:

从Javadoc:

还要注意,尝试从行为参数访问可变状态会给您带来安全性和性能方面的错误选择;
如果您不同步对该状态的访问,则将导致数据争用,因此代码将被破坏,但是如果您确实同步对该状态的访问,则可能会导致争用破坏您要从中受益的并行性
。最好的方法是避免使用有状态的行为参数来完全流式处理操作。通常有一种方法可以重组流管道以避免状态化。

这里的问题是,如果您访问可变状态,则会在两个方面松动:

  • 安全,因为您需要Stream尽量减少同步的同步
  • 性能,因为所需的同步使您付出了代价(在您的示例中,如果使用ConcurrentHashMap,则要付出代价)。

现在,在您的示例中,这里有几点:

  • 如果要使用Stream多线程流,则需要使用parralelStream()in myThings.parralelStream();就目前而言,所forEach提供的方法java.util.Collection很简单for each
  • HashMap用作static成员并对其进行突变。HashMap不是线程安全的;您需要使用ConcurrentHashMap

在lambda中,对于a Stream,您不得更改流的源:

myThings.stream().forEach(thing -> myThings.remove(thing));

这可能有效(但我怀疑会抛出ConcurrentModificationException),但这可能不起作用:

myThings.parallelStream().forEach(thing -> myThings.remove(thing));

那是因为ArrayList不是线程安全的。

如果您使用同步视图(Collections.synchronizedList),则将获得性能,因为您在每次访问时都进行了同步。

在您的示例中,您宁愿使用:

sortOrderCache = myThings.stream()
                         .collect(Collectors.groupingBy(
                           Thing::getId, Thing::getSortOrder);
codeNameCache= myThings.stream()
                       .collect(Collectors.groupingBy(
                         Thing::getId, Thing::getCodeName);

装订器(此处为groupingBy)完成您正在做的工作,并且可能会被顺序调用(我的意思是,Stream可能会拆分为多个线程,装订器可能会被调用多次(在不同的线程中),然后可能需要合并。

顺便说一句,您最终可能会删除codeNameCache/ sortOrderCache并仅存储id-> Thing映射。



 类似资料:
  • 问题内容: 此问题与偶尔获取SqlException:超时expired有关。实际上,我在我的应用程序中使用率很高。但是用户Remus Rusanu说您不应该使用此功能。为什么我不应该使用它以及它包括什么危险。所以,如果我有 如何重写此语句以使其起作用? 问题答案: 使用合并 您的SQL失败,因为2个并发的重叠调用和非常接近的调用都将在INSERT发生之前从EXISTS中获得“ false”。因此

  • 问题内容: 为什么这么危险? 为什么建议改为使用? 我知道已弃用。还有什么使它不安全的? 有什么地方可以使用方法吗?如果是这样,请举一个例子。 问题答案: 为什么Thread.stop()如此危险? 此处详细描述了这些问题:http : //download.oracle.com/javase/6/docs/technotes/guides/concurrency/threadPrimitiveD

  • 我使用Workbox预缓存渲染应用程序外壳所需的资产,包括基本版本的索引。html。Workbox假定索引。html在缓存中可用,否则,页面导航将失败,因为我已在我的Service Worker中注册了此项: 我也有自己。安装侦听器中的skipWaiting()指令: 据我所知,现在有2个侦听器: 由Workbox注册用于预缓存资产(包括index.html)的工具 我在Service Worke

  • 问题内容: GoogleJavaScript样式指南建议不要扩展。但是,我用它来在不存在的浏览器中使用它(以及类似方法)。 我知道一些问题,但不是哈希表。 扩展时可能会出现什么问题,使Google对此提出建议? 问题答案: 大多数人都错过了这一点。我认为,例如填充或填充标准功能,使其在较旧的浏览器中运行,是一个好主意。不要听那些讨厌的人。Mozilla甚至向您展示了如何在MDN上执行此操作。通常,

  • 问题内容: 据说Go的不是(请参见此处和此处)。我有兴趣找出在我忽略使用互斥锁/ etc保护访问地图的情况下可能发生的情况。 具体来说 ,是否会发生以下任何情况? 假设我有一个带有键,,…,的映射,当我要求(i!= j)时,并发问题会导致获取错误吗? 可以在应用程序中导致吗? 问题答案: 正如评论已经指出的那样,种族是不好的。与Java不同,Go具有非常弱的保证,因此, 即使不执行包含种族的代码

  • 问题内容: 我们所有人都被告知,将目录或文件留在基于Linux的Web托管上的权限级别是一件坏事,并且总是根据需要设置尽可能少的权限。 我现在好奇的地方 正是 在于剥削的危险,特别是在一个PHP / Apache的环境。 毕竟,无论是否标记为“可执行文件”,都可以从外部执行PHP脚本文件(即,通过调用Web服务器,然后调用解释器),不是吗?同样适用于通过命令行解释器调用的文件,对吗? 那么该漏洞的