modCount到底是干什么的?

在ArrayList,LinkedList,HashMap等等的内部实现增,删,改中我们总能看到modCount的身影,modCount字面意思就是修改次数,但为什么要记录modCount的修改次数呢? 
大家发现一个公共特点没有,所有使用modCount属性的全是线程不安全的,这是为什么呢?说明这个玩意肯定和线程安全有关系喽,那有什么关系呢

阅读源码,发现这玩意只有在本数据结构对应迭代器中才使用,以HashMap为例:

  1. private abstract class HashIterator<E> implements Iterator<E> {
  2. Entry<K,V> next; // next entry to return
  3. int expectedModCount; // For fast-fail
  4. int index; // current slot
  5. Entry<K,V> current; // current entry
  6.  
  7. HashIterator() {
  8. expectedModCount = modCount;
  9. if (size > 0) { // advance to first entry
  10. Entry[] t = table;
  11. while (index < t.length && (next = t[index++]) == null)
  12. ;
  13. }
  14. }
  15.  
  16. public final boolean hasNext() {
  17. return next != null;
  18. }
  19.  
  20. final Entry<K,V> nextEntry() {
  21. if (modCount != expectedModCount)
  22. throw new ConcurrentModificationException();
  23. Entry<K,V> e = next;
  24. if (e == null)
  25. throw new NoSuchElementException();
  26.  
  27. if ((next = e.next) == null) {
  28. Entry[] t = table;
  29. while (index < t.length && (next = t[index++]) == null)
  30. ;
  31. }
  32. current = e;
  33. return e;
  34. }
  35.  
  36. public void remove() {
  37. if (current == null)
  38. throw new IllegalStateException();
  39. if (modCount != expectedModCount)
  40. throw new ConcurrentModificationException();
  41. Object k = current.key;
  42. current = null;
  43. HashMap. this.removeEntryForKey(k);
  44. expectedModCount = modCount;
  45. }
  46. }

由以上代码可以看出,在一个迭代器初始的时候会赋予它调用这个迭代器的对象的mCount,如何在迭代器遍历的过程中,一旦发现这个对象的mcount和迭代器中存储的mcount不一样那就抛异常 
好的,下面是这个的完整解释 
Fail-Fast 机制 
我们知道 java.util.HashMap 不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 Map:注意到 modCount 声明为 volatile,保证线程之间修改的可见性。

所以在这里和大家建议,当大家遍历那些非线程安全的数据结构时,尽量使用迭代器

以下来自:http://blog.csdn.net/upcye/article/details/48752427

下面是源码中的注释,可以参考。

大概意思就是说:在使用迭代器遍历的时候,用来检查列表中的元素是否发生结构性变化(列表元素数量发生改变)了,主要在多线程环境下需要使用,防止一个线程正在迭代遍历,另一个线程修改了这个列表的结构。好好认识下这个异常:ConcurrentModificationException。对了,ArrayList是非线程安全的。

This field is used by the iterator and list iterator implementation returned by the iterator and listIterator methods. If the value of this field changes unexpectedly, the iterator (or list iterator) will throw a ConcurrentModificationException in response to the next, remove, previous, set or add operations. This provides fail-fast behavior, rather than non-deterministic behavior in the face of concurrent modification during iteration.

 

[java] view plain copy
  1. public class TestArrayListIterator {  
  2.     public static void main(String[] args)  {  
  3.         ArrayList<Integer> list = new ArrayList<Integer>();  
  4.         list.add(10);  
  5.         Iterator<Integer> iterator = list.iterator();  
  6.         while(iterator.hasNext()){  
  7.             Integer integer = iterator.next();  
  8.             if(integer==10)  
  9.                 list.remove(integer);   //注意这个地方  
  10.         }  
  11.     }  
  12. }  

题主可以调试下上面的这个程序,会报运行时异常 ConcurrentModificationException

  1. Exception in thread "main" java.util.ConcurrentModificationException
  2. at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
  3. at java.util.AbstractList$Itr.next(Unknown Source)
  4. at com.notify.TestArrayListIterator.main(TestArrayListIterator.java:17)

在使用迭代器遍历的时候,如果使用ArrayList中的remove(int index) remove(Object o) remove(int fromIndex ,int toIndex) add等方法的时候都会修改modCount,在迭代的时候需要保持单线程的唯一操作,如果期间进行了插入或者删除,就会被迭代器检查获知,从而出现运行时异常

事实上在我们remove掉这个元素之后 ,该ArrayList的size()的值就变为了0,而此时Iterator的游标cursor是 1 ,在ArrayList迭代器的hasNext()方法中

  1. public boolean hasNext() {
  2. return cursor != size();
  3. }

当中判断出cursor 确实不等于 size() 哭cry!!!!
然后循环又继续跑了!!!

 

 


如果我们不进行modCount和expectedModCount(创建迭代器的时候将当时的modCount赋值给expectedModCount),这个程序肯定会报ArrayIndexOutOfBoundsException,这样的异常显然不是应该出现的(这些运行时错误都是使用者的逻辑错误导致的,我们的JDK那么高端,不会出现使用错误,我们只抛出使用者造成的错误,而这个错误是设计者应该考虑的),为了避免出现这样的异常,定义了检查。


又想,为什么不把hasNext()的判断改为cursor <=size()呢?但是我们还有可能 add()这样的话就会导致数据混乱,事实上线程安全本身就不允许读的时候被修改

曾经在霸面大众点评时,面试官就出了一道题:

 

[java]  view plain  copy
 
 
  1. ArrayList<String> list = new ArrayList<String>();  
  2.         for(int i =0;i<1000;i++){  
  3.             list.add("sh"+i);  
  4.         }  
  5.   
  6.         for(int i= 0;list.iterator().hasNext();i++){  
  7.         list.remove(i);  
  8.             System.out.println("秘密"+list.get(i) + i);         
  9.         }  

 

 

[java]  view plain  copy
 
 
  1. Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 500, Size: 500  
  2.     at java.util.ArrayList.rangeCheck(ArrayList.java:635)  
  3.     at java.util.ArrayList.remove(ArrayList.java:474)  
  4.     at com.test.StringTest.main(StringTest.java:26)  

 

会报什么错,当时仔细回忆分析了一下remove()的源码,但还是傻逼一样说报空指针错误。。。。。。。。。。。。。

实际实验:在list.remove(i)在i=500时,此时容量也为500,上一次输出打印为秘密:999499,表明在循环至i=500处出错,原因是由于remove方法更改ArrayList中的内容,但是迭代器不知道,违反了modCount设计的初衷。

猜你喜欢

转载自www.cnblogs.com/lcj12121/p/11710076.html