Java集合快速失败和安全失败机制
快速失败机制
是Java集合的一种错误检测机制,当遍历集合时,集合的结构进行了修改,可能会触发"fail-fast"机制
Java.util包中所有集合都被设计为快速失败机制
示例代码
public class TestFailFast { public static void main(String[] args) { testFailFastBySingleThread(); // testFailFastByMultiThread(); } // 单线程测试快速失败机制 private static void testFailFastBySingleThread() { HashMap<String,String> hashMap =new LinkedHashMap<>(); hashMap.put("1","a"); hashMap.put("2","b"); hashMap.put("3","c"); Iterator<Map.Entry<String,String>> iterator=hashMap.entrySet().iterator(); while (iterator.hasNext()) { hashMap.put("4","d"); System.out.println(iterator.next()); } } // 多线程测试快速失败机制 private static void testFailFastByMultiThread() { List<String> list = new ArrayList(); list.add("基础元素"); String listStr = JSONArray.toJSONString(list); System.out.println("主线程list集合:" + listStr); Thread threadOne = new Thread(new AddRunnable(list), "线程一"); Thread threadTwo = new Thread(new RemoveRunnable(list), "线程二"); threadOne.start(); threadTwo.start(); } } class AddRunnable implements Runnable { private List<String> list; public AddRunnable(List<String> list) { this.list = list; } @Override public void run() { System.out.println("线程一开始"); for (int i = 0; i < 10; i++) { list.add(i + ":线程一"); } String listOne = JSONArray.toJSONString(list); System.out.println("线程一list集合:" + listOne); } } class RemoveRunnable implements Runnable { private List<String> list; public RemoveRunnable(List<String> list) { this.list = list; } @Override public void run() { System.out.println("线程二开始"); while (list.iterator().hasNext()) { String str = list.iterator().next(); list.remove(str); } String listOne = JSONArray.toJSONString(list); System.out.println("线程二list集合:" + listOne); } }
执行testFailFastBySingleThread(),运行结果:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719) at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:752) at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:750) at com.zyuan.boot.Java封装类.Collection.TestList.testFailFastBySingleThread(TestList.java:33) at com.zyuan.boot.Java封装类.Collection.TestList.main(TestList.java:11)
执行testFailFastByMultiThread(),运行结果:
主线程list集合:["基础元素"] 线程一开始 线程二开始 线程一list集合:["基础元素","0:线程一","1:线程一","2:线程一","3:线程一","4:线程一","5:线程一","6:线程一","7:线程一","8:线程一","9:线程一"] Exception in thread "线程二" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.zyuan.boot.Java封装类.Collection.RemoveRunnable.run(TestList.java:69) at java.lang.Thread.run(Thread.java:748)
报错分析
可以发现示例代码运行都抛出了异常:ConcurrentModificationException,这个就是"fail-fast"机制
因为线程一通过循环不断修改集合结构,而线程二则是通过iterator遍历集合,在集合的iterator调用.next()方法时,会进行检测,代码如下:
int expectedModCount = modCount; public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } final void checkForComodification() { // 比较modCount和expectedModCount是否相同,不相同则抛出异常 if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
modCount:记录集合的修改次数,也就是add、remove等操作的次数记录,由transient修饰符修饰
在AbstractList里面被定义,其子类可以继承并使用
protected transient int modCount = 0;
而创建iterator的时候,expectedModCount会获取到modCount的值,因此都是相同的
但是如果不相同(一般是modCount > expectedModCount),就说明有其他线程进行了集合结构修改操作
所以抛出异常告知集合已经发生改变
安全失败机制
遍历集合时,不直接在集合内容上访问,而是copy原有集合内容,在copy的集合中遍历,称为"fail-safe"机制
java.util.concurrent包中所有集合都被设计为安全失败机制
代码和结果
public class TestFailSafe { public static void main(String[] args) { ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<String, String>(); concurrentHashMap.put("1","a"); concurrentHashMap.put("2","b"); concurrentHashMap.put("3","c"); Iterator<Map.Entry<String, String>> iterator = concurrentHashMap.entrySet().iterator(); while (iterator.hasNext()) { concurrentHashMap.put("4", "d"); System.out.println(iterator.next()); } } }
执行结果:
1=a 2=b 3=c 4=d
可以看到是没有抛出任何异常的
原理
ConcurrentHashMap:
public final Map.Entry<K,V> next() { Node<K,V> p; if ((p = next) == null) throw new NoSuchElementException(); K k = p.key; V v = p.val; lastReturned = p; advance(); return new MapEntry<K,V>(k, v, map); } final Node<K,V> advance() { Node<K,V> e; if ((e = next) != null) e = e.next; for (;;) { Node<K,V>[] t; int i, n; // must use locals in checks if (e != null) return next = e; if (baseIndex >= baseLimit || (t = tab) == null || (n = t.length) <= (i = index) || i < 0) return next = null; if ((e = tabAt(t, i)) != null && e.hash < 0) { if (e instanceof ForwardingNode) { tab = ((ForwardingNode<K,V>)e).nextTable; e = null; pushState(t, i, n); continue; } else if (e instanceof TreeBin) e = ((TreeBin<K,V>)e).first; else e = null; } if (stack != null) recoverState(n); else if ((index = i + baseSize) >= n) index = ++baseIndex; // visit upper slots if present } }
可以看到ConcurrentHashMap中的next()方法,做了当前对象拷贝的处理