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()方法,做了当前对象拷贝的处理

 
posted @ 2022-12-30 15:50  LonZyuan  阅读(197)  评论(0编辑  收藏  举报