最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

焦点速讯:Java集合快速失败和安全失败机制

来源:博客园

快速失败机制

是Java集合的一种错误检测机制,当遍历集合时,集合的结构进行了修改,可能会触发"fail-fast"机制

Java.util包中所有集合都被设计为快速失败机制


(资料图片仅供参考)

示例代码

public class TestFailFast {​    public static void main(String[] args) {        testFailFastBySingleThread();//        testFailFastByMultiThread();    }​    // 单线程测试快速失败机制    private static void testFailFastBySingleThread() {        HashMap hashMap =new LinkedHashMap<>();        hashMap.put("1","a");        hashMap.put("2","b");        hashMap.put("3","c");        Iterator> iterator=hashMap.entrySet().iterator();        while (iterator.hasNext()) {            hashMap.put("4","d");            System.out.println(iterator.next());        }    }​    // 多线程测试快速失败机制    private static void testFailFastByMultiThread() {        List 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 list;​    public AddRunnable(List 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 list;​    public RemoveRunnable(List 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 concurrentHashMap = new ConcurrentHashMap();        concurrentHashMap.put("1","a");        concurrentHashMap.put("2","b");        concurrentHashMap.put("3","c");        Iterator> iterator = concurrentHashMap.entrySet().iterator();        while (iterator.hasNext()) {            concurrentHashMap.put("4", "d");            System.out.println(iterator.next());        }    }​}

执行结果:

1=a2=b3=c4=d

可以看到是没有抛出任何异常的

原理

ConcurrentHashMap:

public final Map.Entry next() {            Node p;            if ((p = next) == null)                throw new NoSuchElementException();            K k = p.key;            V v = p.val;            lastReturned = p;            advance();            return new MapEntry(k, v, map);}​final Node advance() {            Node e;            if ((e = next) != null)                e = e.next;            for (;;) {                Node[] 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)e).nextTable;                        e = null;                        pushState(t, i, n);                        continue;                    }                    else if (e instanceof TreeBin)                        e = ((TreeBin)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()方法,做了当前对象拷贝的处理

关键词: 抛出异常 发生改变