单线程也可能引发"并发"访问异常
2009-01-06 15:40
260 查看
不要以为只有多线程才有并发访问问题,其实单线程也有。举个例子,对于集合,相信大家经常碰到下面这种异常:
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)
at java.util.AbstractList$Itr.next(AbstractList.java:420)
这个异常是由于并发修改集合元素引起的,大家第一个反应多半是多线程问题,结果可能怎么也找不出问题。这里我就模拟一下单线程引发这个并发问题的例子。
ArrayList<String> list=new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("6");
for(String m:list){
System.out.println(m);
list.add("7");//look here ,problem point
}
上面这个例子只要一执行就会出现异常。为什么呢?
Iterator模式是用于遍历集合类的标准访问方法,我们来看看集合AbstracyList如何创建Iterator。首先AbstractList定义了一个内部类(inner class):
private class Itr implements Iterator {
...
}
而iterator()方法的定义是:
public Iterator iterator() {
return new Itr();
}
因此客户端不知道它通过Iterator it = a.iterator();所获得的Iterator的真正类型。
现在我们关心的是这个申明为private的Itr类是如何实现遍历AbstractList的:
private class Itr implements Iterator {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
Itr类依靠3个int变量(还有一个隐含的AbstractList的引用)来实现遍历,cursor是下一次next()调用时元素的位置,第一次调用next()将返回索引为0的元素。lastRet记录上一次游标所在位置,因此它总是比cursor少1。
变量cursor和集合的元素个数决定hasNext():
public boolean hasNext() {
return cursor != size();
}
方法next()返回的是索引为cursor的元素,然后修改cursor和lastRet的值:
public Object next() {
checkForComodification();
try {
Object next = get(cursor); //注意这里:得到下一个元素
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
expectedModCount表示期待的modCount值,用来判断在遍历过程中集合是否被修改过。AbstractList包含一个
modCount变量,它的初始值是0,当集合每被修改一次时(调用add,remove等方法),modCount加1。因此,modCount如果不
变,表示集合内容未被修改。
public E get(int index) {
rangeCheck(index); //检查范围
checkForComodification();//注意这里:检查是否有被修改
return l.get(index+offset);
}
Itr初始化时用expectedModCount记录集合的modCount变量,此后在必要的地方它会检测modCount的值:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
如果modCount与一开始记录在expectedModeCount中的值不等,说明集合内容被修改过,此时会抛出ConcurrentModificationException。
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:449)
at java.util.AbstractList$Itr.next(AbstractList.java:420)
这个异常是由于并发修改集合元素引起的,大家第一个反应多半是多线程问题,结果可能怎么也找不出问题。这里我就模拟一下单线程引发这个并发问题的例子。
ArrayList<String> list=new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("6");
for(String m:list){
System.out.println(m);
list.add("7");//look here ,problem point
}
上面这个例子只要一执行就会出现异常。为什么呢?
Iterator模式是用于遍历集合类的标准访问方法,我们来看看集合AbstracyList如何创建Iterator。首先AbstractList定义了一个内部类(inner class):
private class Itr implements Iterator {
...
}
而iterator()方法的定义是:
public Iterator iterator() {
return new Itr();
}
因此客户端不知道它通过Iterator it = a.iterator();所获得的Iterator的真正类型。
现在我们关心的是这个申明为private的Itr类是如何实现遍历AbstractList的:
private class Itr implements Iterator {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
Itr类依靠3个int变量(还有一个隐含的AbstractList的引用)来实现遍历,cursor是下一次next()调用时元素的位置,第一次调用next()将返回索引为0的元素。lastRet记录上一次游标所在位置,因此它总是比cursor少1。
变量cursor和集合的元素个数决定hasNext():
public boolean hasNext() {
return cursor != size();
}
方法next()返回的是索引为cursor的元素,然后修改cursor和lastRet的值:
public Object next() {
checkForComodification();
try {
Object next = get(cursor); //注意这里:得到下一个元素
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
expectedModCount表示期待的modCount值,用来判断在遍历过程中集合是否被修改过。AbstractList包含一个
modCount变量,它的初始值是0,当集合每被修改一次时(调用add,remove等方法),modCount加1。因此,modCount如果不
变,表示集合内容未被修改。
public E get(int index) {
rangeCheck(index); //检查范围
checkForComodification();//注意这里:检查是否有被修改
return l.get(index+offset);
}
Itr初始化时用expectedModCount记录集合的modCount变量,此后在必要的地方它会检测modCount的值:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
如果modCount与一开始记录在expectedModeCount中的值不等,说明集合内容被修改过,此时会抛出ConcurrentModificationException。
相关文章推荐
- 单线程也可能引发"并发"访问异常
- EF(Entity Framework)发生错误”正在创建模型,此时不可使用上下文“的解决办法。 正在创建模型,此时不可使用上下文。如果在 OnModelCreating 方法内使用上下文或如果多个线程同时访问同一上下文实例,可能引发此异常。请注意不保证 DbContext 的实例成员和相关类是线程安全的。 临时解决了这个问题,在Context的构造函数中,禁用了自动初始化:
- 黑马程序员--07.集合框架--并发访问异常理解:一个单线程程序的多线程运行思想【个人总结】
- 正在创建模型,此时不可使用上下文“的解决办法。 正在创建模型,此时不可使用上下文。如果在 OnModelCreating 方法内使用上下文或如果多个线程同时访问同一上下文实例,可能引发此异常。请注意不
- EF异常‘在创建模型,此时不可使用上下文。如果在 OnModelCreating 方法内使用上下文或如果多个线程同时访问同一上下文实例,可能引发此异常。 请注意不保证 DbContext 的实例成员’
- 记录ICallbackEventHandler 同时并发访问容易引发的问题
- 由于“xx”是引用封送类的字段,访问上面的成员可能导致运行时异常
- 问题解决——使用CriticalSection后 0xXXXXXXXX处最可能的异常: 0xC0000005: 写入位置 0x00000014 时发生访问冲突
- 选择目录,选择文件夹的COM组件问题。在可以调用 OLE 之前,必须将当前线程设置为单线程单元(STA)模式。请确保您的 Main 函数带有 STAThreadAttribute 标记。 只有将调试器附加到该进程才会引发此异常。
- 记录ICallbackEventHandler 同时并发访问容易引发的问题
- 一次替换FastDFS数据目录引发的文件上传和访问异常
- 问题解决——使用CriticalSection后 0xXXXXXXXX处最可能的异常: 0xC0000005: 写入位置 0x00000014 时发生访问冲突
- xxx.exe 中的 0x005d93af (sss.dll) 处最可能的异常: 0xC0000005: 读取位置 0x00000000 时发生访问冲突
- 0x0FDFE224 (ucrtbased.dll)处(位于.exe 中)引发的异常: 0xC0000005: 写入位置 0x01100000 时发生访问冲突。 如有适用于此异常的处
- 发送Email时候,可能导致异常:未能访问“CDO.Message”对象,的几种情况整理[转]
- 一个 redis 异常访问引发 oom 的案例分析
- 并发访问数据可能存在的问题
- 解决android数据库并发访问异常
- 记录ICallbackEventHandler 同时并发访问容易引发的问题
- 一个 redis 异常访问引发 oom 的案例分析