jdk8中Spliterator的作用
之前的时候看集合部分源码没看完,今天又翻了一下,看到了个东西spliterator,感觉挺陌生。查了一下,网上解读源码的挺多,但没有使用的例子,于是看了下代码,准备自己写个例子试试。
源码部分,灵小帝的博客已经说的很清楚了,摘抄如下:
--------------------------------------------------------------------------------------------------------------------------
Spliterator是什么?
Spliterator是一个可分割迭代器(splitable iterator),可以和iterator顺序遍历迭代器一起看。jdk1.8发布后,对于并行处理的能力大大增强,Spliterator就是为了并行遍历元素而设计的一个迭代器,jdk1.8中的集合框架中的数据结构都默认实现了spliterator,后面我们也会结合ArrayList中的spliterator()一起解析。
Spliterator内部结构
//单个对元素执行给定的动作,如果有剩下元素未处理返回true,否则返回false boolean tryAdvance(Consumer<? super T> action); //对每个剩余元素执行给定的动作,依次处理,直到所有元素已被处理或被异常终止。默认方法调用tryAdvance方法 default void forEachRemaining(Consumer<? super T> action) { do { } while (tryAdvance(action)); } //对任务分割,返回一个新的Spliterator迭代器 Spliterator<T> trySplit(); //用于估算还剩下多少个元素需要遍历 long estimateSize(); //当迭代器拥有SIZED特征时,返回剩余元素个数;否则返回-1 default long getExactSizeIfKnown() { return (characteristics() & SIZED) == 0 ? -1L : estimateSize(); } //返回当前对象有哪些特征值 int characteristics(); //是否具有当前特征值 default boolean hasCharacteristics(int characteristics) { return (characteristics() & characteristics) == characteristics; } //如果Spliterator的list是通过Comparator排序的,则返回Comparator //如果Spliterator的list是自然排序的 ,则返回null //其他情况下抛错 default Comparator<? super T> getComparator() { throw new IllegalStateException(); }
特征值其实就是为表示该Spliterator有哪些特性,用于可以更好控制和优化Spliterator的使用。关于获取比较器getComparator这一个方法,目前我还没看到具体使用的地方,所以可能理解有些误差。特征值如下:(部分属于猜测)
ArrayListSpliterator
static final class ArrayListSpliterator<E> implements Spliterator<E> { //用于存放ArrayList对象 private final ArrayList<E> list; //起始位置(包含),advance/split操作时会修改 private int index; //结束位置(不包含),-1 表示到最后一个元素 private int fence; //用于存放list的modCount private int expectedModCount; ArrayListSpliterator(ArrayList<E> list, int origin, int fence, int expectedModCount) { this.list = list; this.index = origin; this.fence = fence; this.expectedModCount = expectedModCount; } //获取结束位置(存在意义:首次初始化石需对fence和expectedModCount进行赋值) private int getFence() { int hi; ArrayList<E> lst; //fence<0时(第一次初始化时,fence才会小于0): if ((hi = fence) < 0) { //list 为 null时,fence=0 if ((lst = list) == null) hi = fence = 0; else { //否则,fence = list的长度。 expectedModCount = lst.modCount; hi = fence = lst.size; } } return hi; } //分割list,返回一个新分割出的spliterator实例 public ArrayListSpliterator<E> trySplit() { //hi为当前的结束位置 //lo 为起始位置 //计算中间的位置 int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; //当lo>=mid,表示不能在分割,返回null //当lo<mid时,可分割,切割(lo,mid)出去,同时更新index=mid return (lo >= mid) ? null : new ArrayListSpliterator<E>(list, lo, index = mid, expectedModCount); } //返回true 时,只表示可能还有元素未处理 //返回false 时,没有剩余元素处理了。。。 public boolean tryAdvance(Consumer<? super E> action) { if (action == null) throw new NullPointerException(); //hi为当前的结束位置 //i 为起始位置 int hi = getFence(), i = index; //还有剩余元素未处理时 if (i < hi) { //处理i位置,index+1 index = i + 1; @SuppressWarnings("unchecked") E e = (E)list.elementData[i]; action.accept(e); //遍历时,结构发生变更,抛错 if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; } //顺序遍历处理所有剩下的元素 public void forEachRemaining(Consumer<? super E> action) { int i, hi, mc; // hoist accesses and checks from loop ArrayList<E> lst; Object[] a; if (action == null) throw new NullPointerException(); if ((lst = list) != null && (a = lst.elementData) != null) { //当fence<0时,表示fence和expectedModCount未初始化,可以思考一下这里能否直接调用getFence(),嘿嘿? if ((hi = fence) < 0) { mc = lst.modCount; hi = lst.size; } else mc = expectedModCount; if ((i = index) >= 0 && (index = hi) <= a.length) { for (; i < hi; ++i) { @SuppressWarnings("unchecked") E e = (E) a[i]; //调用action.accept处理元素 action.accept(e); } //遍历时发生结构变更时抛出异常 if (lst.modCount == mc) return; } } throw new ConcurrentModificationException(); } public long estimateSize() { return (long) (getFence() - index); } public int characteristics() { //打上特征值:、可以返回size return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; } }
--------------------------------------------------------------------------------------------------------------------------
以上为源码讲解部分,大意是说,这个就是用来多线程并行迭代的迭代器,这个迭代器的主要作用就是把集合分成了好几段,每个线程执行一段,因此是线程安全的。基于这个原理,以及modCount的快速失败机制,如果迭代过程中集合元素被修改,会抛出异常。
我们设计一个测试用例:创建一个长度为100的list,如果下标能被10整除,则该位置数值跟下标相同,否则值为aaaa。然后多线程遍历list,取出list中的数值(字符串aaaa不要)进行累加求和。
测试代码如下:
public class Atest { AtomicInteger count = new AtomicInteger(0); List<String> strList = createList(); Spliterator spliterator = strList.spliterator(); /** * 多线程计算list中数值的和 * 测试spliterator遍历 */ @Test public void mytest(){ for(int i=0;i<4;i++){ new MyThread().start(); } try { Thread.sleep(15000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("结果为:" + count); } class MyThread extends Thread{ @Override public void run() { String threadName = Thread.currentThread().getName(); System.out.println("线程"+threadName+"开始运行-----"); spliterator.trySplit().forEachRemaining(new Consumer() { @Override public void accept(Object o) { if(isInteger((String)o)){ int num = Integer.parseInt(o +""); count.addAndGet(num); System.out.println("数值:"+num+"------"+threadName); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); System.out.println("线程"+threadName+"运行结束-----"); } } private List<String> createList(){ List<String> result = new ArrayList<>(); for(int i=0; i<100; i++){ if(i % 10 == 0){ result.add(i+""); }else{ result.add("aaa"); } } return result; } public static boolean isInteger(String str) { Pattern pattern = Pattern.compile("^[-\\+]?[\\d]*$"); return pattern.matcher(str).matches(); } }
输出结果:
可以看到,有4个线程执行了该方法,并得到了正确的结果,并发遍历执行没有问题。