TA的每日心情  | 开心 2021-12-13 21:45 | 
|---|
 
  签到天数: 15 天 [LV.4]偶尔看看III  
 | 
 
| 
 
 基本上所有的集合类都会有一个叫做快速失败的校验机制,当一个集合在被多个线程修改并访问时,就会出现ConcurrentModificationException 校验机制。它的实现原理就是我们经常提到的modCount修改计数器。如果在读列表时,modCount发生变化则会抛出ConcurrentModificationException异常。这与线程同步是两码事,线程同步是为了保护集合中的数据不被脏读、脏写而设置的。  
 
1、单线程环境下的异常重现  
  
-     public static void main(String[] args)  {
 -         ArrayList<Integer> list = new ArrayList<Integer>();
 -         list.add(2);
 -         Iterator<Integer> iterator = list.iterator();
 -         while(iterator.hasNext()){
 -             Integer integer = iterator.next();
 -             if(integer==2) {
 -                 list.remove(integer);
 -                 //iterator.remove();   //正确写法
 -             }
 -         }
 -     }
 
  复制代码 
  
  在while(iterator.hasNext()) 循环遍历时,只允许删除ArrayList 内部的  elementData[ ] 的最后一个元素,而不允许从中间删除。 
  在 iterator.next()  的源码中,会首先执行方法:checkForComodification();  该方法:  
  
-      final void checkForComodification() {
 -             if (modCount != expectedModCount)
 -                 throw new ConcurrentModificationException();
 -         }
 
  复制代码 
  
  只有当两个变量值相等时才不会报错。而 list.add(2)操作和 list.remove(integer); 操作会使 modCount++;   但是变量 expectedModCount 是内部类的 Itr 子类 的 变量,该子类的实例化方法是:list.iterator();  实例对象时赋初始值: 
int expectedModCount = modCount;   所以,不允许在 while(iterator.hasNext())  循环方法内 有list.add(2)操作和 list.remove(integer);操作,否则会导致expectedModCount != modCount ,进而导致  throw new ConcurrentModificationException();  
  
- public class Vector {
 -     public static void main(String[] args) {
 -         final List<String> tickets = new ArrayList<String>();
 -         for(int i=0;i<100000;i++){
 -             tickets.add("火车票"+i);
 -         }
 -         Thread returnThread = new Thread(){
 -             @Override
 -             public void run() {
 -                while (true){
 -                    tickets.add("车票"+ new Random().nextInt());
 -                }
 -             };
 -         };
 -         Thread saleThread = new Thread(){
 -             @Override
 -             public void run() {
 -                 for (String ticket : tickets){
 -                     tickets.remove(0);
 -                 }
 -             };
 -         };
 -         returnThread.start();
 -         saleThread.start();
 -     }
 - }
 
  复制代码 
  
    
  有可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。  
  原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。  
  因此一般有2种解决办法:  
  1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;  
  2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。  
[code][/code] |   
 
 
 
 |