基于索引的迭代期间没有 ConcurrentModificationException

No ConcurrentModificationException during index based iteration

我有以下代码:

public static void main(String[] args) {
 
    List<String> input = new ArrayList<>();
    List<String> output = new ArrayList<>();
    for(int i=0; i< 1000 ;i++){
        input.add(i+"");
    }
    
    
    for(int i=0 ; i<input.size(); i++){
        String value = input.get(i);
        if(Integer.parseInt(value) % 2 == 0){
            output.add(value);
            input.remove(value);
        }
    }
    
    input.stream().forEach(System.out::println);
    System.out.println("--------------------------------------");
    output.stream().forEach(System.out::println);

}

我预计它会抛出 ConcurrentModificationException 但它工作正常。能解释一下原因吗?

原因是您没有在技术上迭代列表。相反,您使用递增索引随机访问列表,并删除一些值。如果您更改为这样的代码来迭代列表,它将抛出 ConcurrentModificationException

public static void main(String[] args) {
    List<String> input = new ArrayList<>();
    List<String> output = new ArrayList<>();
    for(int i=0; i< 1000 ;i++){
        input.add(i+"");
    }
    
    for (String value : input) {
        if(Integer.parseInt(value) % 2 == 0){
            output.add(value);
            input.remove(value);
        }
    }

    input.stream().forEach(System.out::println);
    System.out.println("--------------------------------------");
    output.stream().forEach(System.out::println);
}

跟进为什么与迭代器相比这可能不是首选方式。原因之一是性能。这是一些使用 JMH 进行测试的基准代码。

package bench;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.SECONDS;

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 1, time = 3, timeUnit = SECONDS)
@Measurement(iterations = 3, time = 2, timeUnit = SECONDS)
public class JmhBenchmark {
    private List<String> input;

    @Param({"100", "1000", "10000"})
    public int length;

    @Setup(Level.Invocation)
    public void createInputList() {
        input = new ArrayList<>();
        for (int i = 0; i < length; i++) {
            input.add(i + "");
        }
    }

    @Benchmark
    public void iterateWithVariable() {
        for (int i = 0; i < input.size(); i++) {
            String value = input.get(i);
            if (Integer.parseInt(value) % 2 == 0) {
                input.remove(value);
            }
        }
    }

    @Benchmark
    public void iterateWithIterator() {
        final Iterator<String> iterator = input.iterator();
        while (iterator.hasNext()) {
            String value = iterator.next();
            if (Integer.parseInt(value) % 2 == 0) {
                iterator.remove();
            }
        }
    }

}

我系统的基准测试结果是

Benchmark                         (length)  Mode  Cnt   Score    Error  Units
JmhBenchmark.iterateWithIterator       100  avgt   15   0.002 ±  0.001  ms/op
JmhBenchmark.iterateWithIterator      1000  avgt   15   0.033 ±  0.001  ms/op
JmhBenchmark.iterateWithIterator     10000  avgt   15   1.670 ±  0.017  ms/op
JmhBenchmark.iterateWithVariable       100  avgt   15   0.005 ±  0.001  ms/op
JmhBenchmark.iterateWithVariable      1000  avgt   15   0.350 ±  0.014  ms/op
JmhBenchmark.iterateWithVariable     10000  avgt   15  33.591 ±  0.455  ms/op

所以我们可以看到使用迭代器从列表中删除一些项目比这个问题提出的方法快很多(> 20 倍)。这是有道理的,您需要在列表中执行随机查找,然后确定是否需要删除它,然后再执行一次查找以找到并删除它。