免费源代码网站,小微宝安网站建设,网站开发容易找工作吗,不收费的企业查询网站背景Java8的stream接口极大地减少了for循环写法的复杂性#xff0c;stream提供了map/reduce/collect等一系列聚合接口#xff0c;还支持并发操作#xff1a;parallelStream。在爬虫开发过程中#xff0c;经常会遇到遍历一个很大的集合做重复的操作#xff0c;这时候如果使…背景Java8的stream接口极大地减少了for循环写法的复杂性stream提供了map/reduce/collect等一系列聚合接口还支持并发操作parallelStream。在爬虫开发过程中经常会遇到遍历一个很大的集合做重复的操作这时候如果使用串行执行会相当耗时因此一般会采用多线程来提速。Java8的paralleStream用fork/join框架提供了并发执行能力。但是如果使用不当很容易陷入误区。Java8的paralleStream是线程安全的吗一个简单的例子,在下面的代码中采用stream的forEach接口对1-10000进行遍历分别插入到3个ArrayList中。其中对第一个list的插入采用串行遍历第二个使用paralleStream第三个使用paralleStream的同时用ReentryLock对插入列表操作进行同步private static List list1 new ArrayList();private static List list2 new ArrayList();private static List list3 new ArrayList();private static Lock lock new ReentrantLock();public static void main(String[] args) {IntStream.range(0, 10000).forEach(list1::add);IntStream.range(0, 10000).parallel().forEach(list2::add);IntStream.range(0, 10000).forEach(i - {lock.lock();try {list3.add(i);}finally {lock.unlock();}});System.out.println(串行执行的大小 list1.size());System.out.println(并行执行的大小 list2.size());System.out.println(加锁并行执行的大小 list3.size());}执行结果串行执行的大小10000并行执行的大小9595加锁并行执行的大小10000并且每次的结果中并行执行的大小不一致而串行和加锁后的结果一直都是正确结果。显而易见stream.parallel.forEach()中执行的操作并非线程安全。那么既然paralleStream不是线程安全的是不是在其中的进行的非原子操作都要加锁呢我在stackOverflow上找到了答案https://codereview.stackexchange.com/questions/60401/using-java-8-parallel-streamshttps://stackoverflow.com/questions/22350288/parallel-streams-collectors-and-thread-safety在上面两个问题的解答中证实paralleStream的forEach接口确实不能保证同步同时也提出了解决方案使用collect和reduce接口。http://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html在Javadoc中也对stream的并发操作进行了相关介绍The Collections Framework provides synchronization wrappers, which add automatic synchronization to an arbitrary collection, making it thread-safe.Collections框架提供了同步的包装使得其中的操作线程安全。所以下一步来看看collect接口如何使用。stream的collect接口闲话不多说直接上源码吧Stream.java中的collect方法句柄 R collect(Collector super T, A, R collector);在该实现方法中参数是一个Collector对象可以使用Collectors类的静态方法构造Collector对象比如Collectors.toList()toSet(),toMap()etc这块很容易查到API故不细说了。除此之外我们如果要在collect接口中做更多的事就需要自定义实现Collector接口需要实现以下方法Supplier supplier();BiConsumer accumulator();BinaryOperator combiner();Function finisher();Set characteristics();要轻松理解这三个参数要先知道fork/join是怎么运转的一图以蔽之上图来自http://www.infoq.com/cn/articles/fork-join-introduction简单地说就是大任务拆分成小任务分别用不同线程去完成然后把结果合并后返回。所以第一步是拆分第二步是分开运算第三步是合并。这三个步骤分别对应的就是Collector的supplier,accumulator和combiner。talk is cheap show me the code下面用一个例子来说明输入是一个10个整型数字的ArrayList通过计算转换成double类型的Set首先定义一个计算组件Compute.java:public class Compute {public Double compute(int num) {return (double) (2 * num);}}接下来在Main.java中定义输入的类型为ArrayList的nums和类型为Set的输出结果resultprivate List nums new ArrayList();private Set result new HashSet();定义转换list的run方法实现Collector接口调用内部类Container中的方法其中characteristics()方法返回空set即可public void run() {// 填充原始数据nums中填充0-9 10个数IntStream.range(0, 10).forEach(nums::add);//实现Collector接口result nums.stream().parallel().collect(new Collector() {Overridepublic Supplier supplier() {return Container::new;}Overridepublic BiConsumer accumulator() {return Container::accumulate;}Overridepublic BinaryOperator combiner() {return Container::combine;}Overridepublic Function finisher() {return Container::getResult;}Overridepublic Set characteristics() {// 固定写法return Collections.emptySet();}});}构造内部类Container该类的作用是一个存放输入的容器定义了三个方法accumulate方法对输入数据进行处理并存入本地的结果combine方法将其他容器的结果合并到本地的结果中getResult方法返回本地的结果Container.java:class Container {// 定义本地的resultpublic Set set;public Container() {this.set new HashSet();}public Container accumulate(int num) {this.set.add(compute.compute(num));return this;}public Container combine(Container container) {this.set.addAll(container.set);return this;}public Set getResult() {return this.set;}}在Main.java中编写测试方法public static void main(String[] args) {Main main new Main();main.run();System.out.println(原始数据);main.nums.forEach(i - System.out.print(i ));System.out.println(\n\ncollect方法加工后的数据);main.result.forEach(i - System.out.print(i ));}输出原始数据0 1 2 3 4 5 6 7 8 9collect方法加工后的数据0.0 2.0 4.0 8.0 16.0 18.0 10.0 6.0 12.0 14.0我们将10个整型数值的list转成了10个double类型的set至此验证成功本程序参考 http://blog.csdn.net/io_field/article/details/54971555。一言蔽之总结就是paralleStream里直接去修改变量是非线程安全的但是采用collect和reduce操作就是满足线程安全的了。总结以上就是这篇文章的全部内容了希望本文的内容对大家的学习或者工作具有一定的参考学习价值如果有疑问大家可以留言交流谢谢大家对脚本之家的支持。