Java 集合框架04-fail-fast总结

简介: 上一篇我们介绍了ArrayList的相关源码,这篇我们将了解一下fail-fast机制的相关知识

上一篇我们介绍了ArrayList的相关源码,这篇我们将了解一下fail-fast机制的相关知识


1.fail-fast的简介

2.fail-fast的相关示例

3.fail-fast的解决办法

4.fail-fast的原理

解决fail-fast的原理


fail-fast的简介


fail-fast机制是java 集合的一种错误机制。 当多个线程对同一个集合的内容进行操作时,就会产生fail-fast事件。

例如:当线程A通过iterator迭代器(或者 For和RandomAccess)来访问集合时,线程B对集合的内容进行了修改,则线程A在访问集合时就会抛出ConcurrentModificationException异常,产生fail-fast事件。


fail-fast的相关示例

package com.jay.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
 * Created by xiang.wei on 2018/3/4
 *
 * @author xiang.wei
 */
public class FailFastTest {
    /**
     *
     */
    private static List<String> globalList = new ArrayList<String>();
    /**
     *  线程one,two,three同时启动,线程one向globalList中添加元素0,1,2,3,4,5,然后遍历打印
     *  线程two 与此同时也向globalList中添加元素10,11,12,13,14,15,然后遍历打印,
     *  线程three 同时也在移除globalList中元素0,1,2,3,4,5,然后遍历打印
     *
     */
    public static void main(String[] args) {
        new ThreadOne().start();
        new ThreadTwo().start();
        new ThreadThree().start();
    }
    /**
     * 向globalList中添加元素0,1,2,3,4,5,然后遍历打印
     */
    private static class ThreadOne extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 6; i++) {
                globalList.add(String.valueOf(i));
                printAll(globalList);
            }
        }
    }
    /**
     *  向globalList中添加元素10,11,12,13,14,15,然后遍历打印
     */
    private static class ThreadTwo extends Thread {
        @Override
        public void run() {
            for (int i = 10; i < 16; i++) {
                globalList.add(String.valueOf(i));
                printAll(globalList);
            }
        }
    }
    /**
     * 移除globalList中元素0,1,2,3,4,5,然后遍历打印
     */
    private static class ThreadThree extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 6; i++) {
                globalList.remove(String.valueOf(i));
                printAll(globalList);
            }
        }
    }
    private static void printAll(List<String> testList) {
        Iterator<String> testIter = testList.iterator();
        while (testIter.hasNext()) {
            System.out.println((testIter.next()));
        }
    }
}

26d132c275edf55da962c8e217f977f5_SouthEast.png


fail-fast的解决办法

按照Java api文档的建议,在多线程环境中我们需要选用java.util.concurrent包下的CopyOnWriteArrayList集合,

即将

private static List<String> globalList = new ArrayList<String>();

替换成

private static List<String> globalList = new CopyOnWriteArrayList<String>();

fail-fast的原理

我们此处以ArrayList为例,其Iterator的实现是在AbstractList中

private class Itr implements Iterator<E> {
 int cursor = 0;
 int expectedModCount = modCount;
     public boolean hasNext() {
            return cursor != size;
        }
        @SuppressWarnings("unchecked")
        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];
        }
         public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
      final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }


首先,定义一个实例变量

protected transient int modCount = 0;


当实例化一个迭代器Itr时,会将modCount的值赋给expectedModCount,以后每次迭代时都会检查这两个值是否相等,如果不相等则会抛出ConcurrentModificationException。显然,expectedModCount不会改变,那么哪些操作会修改modCount的值呢?

(ps: 这也解释了为啥iterator为啥可以直接移除当前元素)

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
         //增加modCount的值
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
        public E remove(int index) {
        rangeCheck(index);
         //增加modCount的值
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }


从上述源码中我们可以看出 add,remove方法修改modCount的值。


解决fail-fast的原理


我们接着看看CopyOnWriteArrayList是如何解决fail-fast的问题的。

首先CopyOnWriteArrayList的定义

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}


CopyOnWriteArrayList直接实现了List,其iterator的迭代器COWIterator是在自身类实现的。

static final class COWIterator<E> implements ListIterator<E> {
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;
 private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
        public boolean hasNext() {
            return cursor < snapshot.length;
        }
        public boolean hasPrevious() {
            return cursor > 0;
        }
        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }


(01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。

(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。

(03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!


引用

http://www.cnblogs.com/skywang12345/p/3308762.html


相关文章
|
2月前
|
Java 数据库
在Java中使用Seata框架实现分布式事务的详细步骤
通过以上步骤,利用 Seata 框架可以实现较为简单的分布式事务处理。在实际应用中,还需要根据具体业务需求进行更详细的配置和处理。同时,要注意处理各种异常情况,以确保分布式事务的正确执行。
|
7天前
|
存储 安全 Java
Java 集合框架中的老炮与新秀:HashTable 和 HashMap 谁更胜一筹?
嗨,大家好,我是技术伙伴小米。今天通过讲故事的方式,详细介绍 Java 中 HashMap 和 HashTable 的区别。从版本、线程安全、null 值支持、性能及迭代器行为等方面对比,帮助你轻松应对面试中的经典问题。HashMap 更高效灵活,适合单线程或需手动处理线程安全的场景;HashTable 较古老,线程安全但性能不佳。现代项目推荐使用 ConcurrentHashMap。关注我的公众号“软件求生”,获取更多技术干货!
30 3
|
2月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
2月前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
179 3
|
24天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
41 5
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
46 4
|
2月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
71 2
|
2月前
|
前端开发 Java 数据库连接
你不可不知道的JAVA EE 框架有哪些?
本文介绍了框架的基本概念及其在编程领域的应用,强调了软件框架作为通用、可复用的软件环境的重要性。文章分析了早期Java EE开发中使用JSP+Servlet技术的弊端,包括可维护性差和代码重用性低等问题,并阐述了使用框架的优势,如提高开发效率、增强代码规范性和可维护性及提升软件性能。最后,文中详细描述了几种主流的Java EE框架,包括Spring、Spring MVC、MyBatis、Hibernate和Struts 2,这些框架通过提供强大的功能和支持,显著提升了Java EE应用的开发效率和稳定性。
131 1
|
2月前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
37 2
|
2月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。