基于索引的迭代期间没有 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 倍)。这是有道理的,您需要在列表中执行随机查找,然后确定是否需要删除它,然后再执行一次查找以找到并删除它。
我有以下代码:
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 倍)。这是有道理的,您需要在列表中执行随机查找,然后确定是否需要删除它,然后再执行一次查找以找到并删除它。