本文转载自微信公众号“小姐姐的味道”,作者小姐姐养的狗。转载本文请联系味觉小姐公众号。很久以前xjjdog有一篇文章详细分析了为什么不能随便用parallelstream,因为里面坑多肉少,隐藏着很多不为人知的超级恶心的小秘密。parallelStream的坑,不踩不知道,一踩就吓一跳,今天又在网上报错遇到了。与parallelStream相比,它可能会让程序运行的更慢(是的),更要命的是它会让你的程序抛出异常,运行变得不准确。当最终确定了根本问题时,一种恶心的感觉涌上心头。我干呕了几声,心情溢于言表。这一幕在我们上一篇文章中被判定为小儿科。但是就算是这样的童书代码,还是有人被坑了,对并发编程还是要有一点敬畏之心,对API不是很了解才可以使用。我们先来看看这段小代码。Listtransform(Listsource){Listdst=newArrayList<>();if(CollectionUtils.isEmpty()){returndst;}source.stream..parallel().map(..).filter(..).foreach(dst::add);returndst;}该程序非常简单。期望通过stream的方式,将一个列表转换过滤后转换成另一个列表。特别是,代码使用了parallel(),也就是说底层会通过forkjoin来运行你的代码。上线后,APP反应异常。在返回的List中,有些数据时而出现,时而消失,就像阿里公关下的热搜一样,变成了鬼数据。更有趣的是,它还会抛出异常。追根究底,明眼人一看到parallelell这个关键词,就恨之入骨。在并行方法中使用线程不安全的集合类是Java编程的大忌。让我们强行去除这些干扰因素来模拟这种数据丢失的情况。publicclassxx{staticListtransform(Listsource){Listdst=newArrayList<>();source.stream().parallel().map(i->i*10).forEach(dst::add);returndst;}publicstaticvoidmain(String[]args){Listsource=newArrayList<>();for(inti=0;i<500;i++){source.add(i);}for(inti=0;i<100;i++){System.out.println("size="+transform(source).size());}}}我们主要转换500条数据。很快,您就会看到异常。但大多数情况下,数据项根本达不到500条,部分数据就莫名其妙的消失了。大小=499size=500size=484size=500Exceptioninthread"main"java.lang.ArrayIndexOutOfBoundsExceptionatsun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeMethod)atsun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)atsun.reflect.DelegatingConstructorImplatingConstructor.newInstanceAccessorIjava:45)在java.lang.reflect.Constructor.newInstance(Constructor.java:423)在java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)在java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)atjava.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)既然是并行,用屁股想想,就知道肯定有线程安全问题。但是我们这里讨论的不是让你使用线程安全的集合,这个话题太低级了。在这个阶段,知道如何在线程不安全的环境下使用线程安全的集合已经是基本功了。我现在收回上述内容,因为我发现这不是一项基本技能。对于ArrayList,它的add操作不是线程安全的,也不是原子操作。publicbooleanadd(Ee){ensureCapacityInternal(size+1);elementData[size++]=e;returntrue;}你看上面的代码有多明显,首先要读取size的值,然后就是加1的操作,然后是另一个自增操作。这种代码是不敢在多线程环境下使用的。总结同样的道理,你不是在做普通的地图或普通的队列,这些都不是安全的操作。还是建议大家看一下它比较隐藏的坑。parallelStream的坑,不踩不知道,踩了会吓一跳。事实上,我真的不建议你在任何时候使用parallelStream或parallel函数。一旦你在代码中找到它,杀掉它并吐在它身上,就好像它从未存在于jdk中一样。它引入的问题比它增加速度的纳秒更严重。事实上,我已经将它添加到声纳的检测规则中,使其完全从我的视野中消失。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。