为什么当多个线程使用迭代器同时处理同一个数组列表时,这段代码不会抛出 ConcurrentModificationException
Why this code doesn't throw ConcurrentModificationException when multiple threads work on same arraylist at the same time using iterator
此处有 2 个线程处理同一个数组列表,一个线程读取元素,另一个线程删除特定元素。我希望这会抛出 ConcurrentModificationException
。但是它不抛出为什么?
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
public class IteratorStudies {
public static final ArrayList<String> arr ;
static{
arr = new ArrayList<>();
for(int i=0;i<100;i++) {
arr.add("someCommonValue");
}
arr.add("someSpecialValue");
}
private static Integer initialValue = 4;
public static void main(String x[]) {
Thread t1 = new Thread(){
@Override
public void start(){
Iterator<String> arrIter = arr.iterator();
while(arrIter.hasNext()){
try {
String str = arrIter.next();
System.out.println("value :" + str);
}catch(ConcurrentModificationException e){
e.printStackTrace();
}
}
System.out.println("t1 complete:"+arr);
}
};
Thread t2 = new Thread(){
@Override
public void start(){
Iterator<String> arrIter = arr.iterator();
while(arrIter.hasNext()){
String str = arrIter.next();
if(str.equals("someSpecialValue")){
arrIter.remove();
}
}
System.out.println("t2 complete:"+arr);
}
};
t2.start();
t1.start();
}
}
您已经为两个线程实例覆盖了 start
方法,而不是 run
,并且这些方法在主执行线程中完成,因此,不会同时执行线程,也不会 ConcurrentModificationThreadException
可以在这里发生。
你犯了 2 个比较常见的错误。
ConcurrentModificationException 不是关于并发
你会认为,鉴于这个名字,CoModEx 是关于并发的。不是。就像,你不需要线程来获取它。在这里,这个简单的代码将抛出它:
void example() {
var list = new ArrayList<String>();
list.add("a");
list.add("b");
for (String elem : list) {
if (elem.equals("a")) list.remove(elem);
}
}
那是因为 CoModEx 是由迭代器抛出的,只是意味着这件事发生了:
- 有人做了一个迭代器。
- 有人以某种方式更改了列表(而不是通过迭代器的 .remove() 方法)
- 某人运行在#1
中制作的迭代器上的任何相关方法
所以,在上面,foreach 循环隐式地创建了一个迭代器(#1),然后 list.remove
方法被调用(#2),然后通过再次点击 foreach 循环,我们调用了一个相关的该迭代器 (.hasNext()
) 上的方法,瞧,CoModEx 发生了。
事实上,多线程 的可能性较小:毕竟,您应该假设如果您从多个线程与某个对象进行交互,那么 它已损坏,因为这种行为是未指定的,因此,你有一个错误,更糟糕的是,很难测试一个错误。如果你在迭代它的同时从另一个线程修改一个普通的简数组列表,你不能保证一个CoModEx。你可能会明白。你不可以。电脑可能会离开办公桌,在百老汇碰碰运气。 “未指定的行为”是一种很好的说法:“不要,说真的。它会一直伤害你,因为你无法测试它;这将在你开发它的整个过程中正常工作,并且只要你付出对大假发客户的重要演示,它会以令人尴尬的方式在你身上失败。
从多个线程与一个对象交互的方式非常小心:检查特定对象的文档明确说明发生了什么(即使用 java.util.concurrent
包中的东西,它是专门用 'interact with it from more than one thread' 牢记用例),否则,请使用锁定。这些都是棘手的事情,所以在 java 中执行多线程的通常方法是首先不要共享状态。尽可能多地隔离,反转控制,并使用具有内置事务内在特性的消息传递策略,例如消息队列(rabbitmq 和朋友)和数据库(具有事务)。
如何使用线程
您重写 run()
方法,然后通过调用 start
方法启动线程。或者更好的是,不要覆盖 运行,在创建线程实例时传递一个 Runnable 实例。
这就是你使用线程的方式。你没有 - 你覆盖了开始,这意味着启动这些线程根本不会创建一个新线程,它只是 运行 是你线程中的有效负载。这解释了您的具体情况,但是您尝试做的事情(通过弄乱另一个线程的列表来证明 CoModEx)也不会让您获得 CoModEx - 它会让您出现未指定的行为,这意味着一切都会发生。
此处有 2 个线程处理同一个数组列表,一个线程读取元素,另一个线程删除特定元素。我希望这会抛出 ConcurrentModificationException
。但是它不抛出为什么?
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
public class IteratorStudies {
public static final ArrayList<String> arr ;
static{
arr = new ArrayList<>();
for(int i=0;i<100;i++) {
arr.add("someCommonValue");
}
arr.add("someSpecialValue");
}
private static Integer initialValue = 4;
public static void main(String x[]) {
Thread t1 = new Thread(){
@Override
public void start(){
Iterator<String> arrIter = arr.iterator();
while(arrIter.hasNext()){
try {
String str = arrIter.next();
System.out.println("value :" + str);
}catch(ConcurrentModificationException e){
e.printStackTrace();
}
}
System.out.println("t1 complete:"+arr);
}
};
Thread t2 = new Thread(){
@Override
public void start(){
Iterator<String> arrIter = arr.iterator();
while(arrIter.hasNext()){
String str = arrIter.next();
if(str.equals("someSpecialValue")){
arrIter.remove();
}
}
System.out.println("t2 complete:"+arr);
}
};
t2.start();
t1.start();
}
}
您已经为两个线程实例覆盖了 start
方法,而不是 run
,并且这些方法在主执行线程中完成,因此,不会同时执行线程,也不会 ConcurrentModificationThreadException
可以在这里发生。
你犯了 2 个比较常见的错误。
ConcurrentModificationException 不是关于并发
你会认为,鉴于这个名字,CoModEx 是关于并发的。不是。就像,你不需要线程来获取它。在这里,这个简单的代码将抛出它:
void example() {
var list = new ArrayList<String>();
list.add("a");
list.add("b");
for (String elem : list) {
if (elem.equals("a")) list.remove(elem);
}
}
那是因为 CoModEx 是由迭代器抛出的,只是意味着这件事发生了:
- 有人做了一个迭代器。
- 有人以某种方式更改了列表(而不是通过迭代器的 .remove() 方法)
- 某人运行在#1 中制作的迭代器上的任何相关方法
所以,在上面,foreach 循环隐式地创建了一个迭代器(#1),然后 list.remove
方法被调用(#2),然后通过再次点击 foreach 循环,我们调用了一个相关的该迭代器 (.hasNext()
) 上的方法,瞧,CoModEx 发生了。
事实上,多线程 的可能性较小:毕竟,您应该假设如果您从多个线程与某个对象进行交互,那么 它已损坏,因为这种行为是未指定的,因此,你有一个错误,更糟糕的是,很难测试一个错误。如果你在迭代它的同时从另一个线程修改一个普通的简数组列表,你不能保证一个CoModEx。你可能会明白。你不可以。电脑可能会离开办公桌,在百老汇碰碰运气。 “未指定的行为”是一种很好的说法:“不要,说真的。它会一直伤害你,因为你无法测试它;这将在你开发它的整个过程中正常工作,并且只要你付出对大假发客户的重要演示,它会以令人尴尬的方式在你身上失败。
从多个线程与一个对象交互的方式非常小心:检查特定对象的文档明确说明发生了什么(即使用 java.util.concurrent
包中的东西,它是专门用 'interact with it from more than one thread' 牢记用例),否则,请使用锁定。这些都是棘手的事情,所以在 java 中执行多线程的通常方法是首先不要共享状态。尽可能多地隔离,反转控制,并使用具有内置事务内在特性的消息传递策略,例如消息队列(rabbitmq 和朋友)和数据库(具有事务)。
如何使用线程
您重写 run()
方法,然后通过调用 start
方法启动线程。或者更好的是,不要覆盖 运行,在创建线程实例时传递一个 Runnable 实例。
这就是你使用线程的方式。你没有 - 你覆盖了开始,这意味着启动这些线程根本不会创建一个新线程,它只是 运行 是你线程中的有效负载。这解释了您的具体情况,但是您尝试做的事情(通过弄乱另一个线程的列表来证明 CoModEx)也不会让您获得 CoModEx - 它会让您出现未指定的行为,这意味着一切都会发生。