本帖最后由 lzhuas 于 2014-5-28 23:43 编辑
刚才我复习功课,复习到了多线程的同步,突然想起了视频里毕老师说的List集合中的迭代并发异常的那一部分,我又想到7K里的异步。。。
经过这一连串的疑惑我就想,是不是List是不安全的,迭代里突然两个线程同时操作着同一个集合的数据,就发生了并发异常。为了一究到底,我百度了。以下是解释,希望大家能学习学习:
不要以为只有多线程才有并发访问问题,其实单线程也有。举个例子,对于集合,相信大家经常碰到下面这种异常: - 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。
大家看得很费劲吧?我最后解释一下吧,也就是迭代里有一个安全机制,它会检查进来时和出去时的记录值,就是看修改了没有。要是修改了就发生并发异常,这就是根本原因。如果您有什么别的见解请大声说出来也好让我纠正,谢谢!
|