之前的時候看集合部分源碼沒看完,今天又翻了一下,看到了個東西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個線程執行了該方法,並得到了正確的結果,並發遍歷執行沒有問題。
