背景
线上有个接口需要查询返回一个列表数据,原来是采用了Stream进行遍历数据过滤的,随着数据的增长,这个接口一次性需要处理的数据越来越多,然后这期需要对其进行优化。
除去一些其它的DB方法优化,也改动到这里了,改成了parallelStream进行处理。没做好并发处理,埋了个雷!
经过优化后,性能提升也还算比较明显有用,极端数据的情况下缩短了近一半开销。
效果这么好,我笑了
事故
然后顺利的通过测试,发布上线...结果第二天就出现异常,还是偶发性异常,根据日志本地重现也无法重现,最后查了下具体的报错信息。数组越界ArrayIndexOutOfBoundsException
知道错误信息,定位到错误行,发现只把stream改成了parallelStream而已,没想到引发了bug。
可以看到git记录只改动了这一个地方,就出现了异常,但是逐行看去发现其中有个操作list的操作,这个有点不对劲。
在采用 stream串行流的时候是没有问题的,数据都是一个个往前走的。但是改成parallelStream并行流就有可能出问题了,因为并行流是多个线程之间一起进行的,就有概率抢占到一起操作这个list(retainAll()保留两个list中都存在的),而我们知道list中的ArrayList是线程不安全的,这就会导致A线程和B线程同时操作这个list
重现
知道了问题所在就好办了,知道前因后果,那重现一下。
可以看到,我们执行后就有可能出现这个异常错误了。
问:那如果非要这么操作list,又要使用parallelStream串行流提高效率,应该怎么处理呢?
答:既然知道同一个list可能会被多个线程操作,那我们是不是可以在每个线程上自己创建一个新的list呢?答案是可以的,在操作list前new一个新的list再操作,这样就避免了
可以看到改造list后的执行,可以顺利执行并且不会抛出数组异常了。
总结
任何好的工具都是一把双刃剑,好用的同时也得注意别伤到自己了。
parallelStream用好了效率加倍,用不好就加班。
具体parallelStream的介绍可以看看其它大佬的介绍和详细文档,还有不少坑需要注意。
以后改动的时候还是要多看看其中的业务代码和操作,避免搭配使用又出现BUG。