Java fast-fail机制

在对非线程安全的List或Map使用迭代器进行遍历的过程中,如果有其它线程修改了List或Map,那么将抛出ConcurrentModificationException,这即所谓fast-fail机制。

这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map。
注意到modCount声明为volatile,保证线程之间修改的可见性。 

在HashMap的API中有如下描述:

   由所有HashMap类的"collection" 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,
   迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。

   注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。
   快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:
   迭代器的快速失败行为应该仅用于检测程序错误。

JDK1.0引入了第一个关联的集合类HashTable,它是线程安全的。HashTable的所有方法都是同步的。
JDK2.0引入了HashMap,它提供了一个不同步的基类和一个同步的包装器synchronizedMap。synchronizedMap被称为有条件的线程安全类。
JDK5.0 java.util.concurrent包中引入对Map线程安全的实现ConcurrentHashMap,比起synchronizedMap,它提供了更高的灵活性。
同时进行的读和写操作都可以并发地执行。

所以在开始的测试中,如果我们采用ConcurrentHashMap,它的表现就很稳定,所以以后如果使用Map实现本地缓存,
为了提高并发时的稳定性,还是建议使用ConcurrentHashMap。

ArrayList也是非线程安全的,网上看到的有一个解释是这样:
一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也将元素放在位置0,(因为size还未增长),完了之后,两个线程都是size++,结果size变成2,而只有items[0]有元素。
java.util.concurrent包也提供了一个线程安全的ArrayList替代者CopyOnWriteArrayList


拓展思考

Vector与CopyOnWriteArrayList的区别:

Vector是线程安全的,它的add、remove、get、set方法都做了线程同步,虽说Vector是线程安全的,但由于迭代遍历时集合仍可能被修改,所以依然会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList只是对add、remove、set方法使用ReentrantLock做了同步,get方法并未做同步。而且在add、remove、set时,CopyOnWriteArrayList会先对数组进行拷贝(这里性能会特别低),然后把要add或remove的元素在新数组中进行操作。这样做的好处是,在使用迭代器遍历集合的时候不会再出现ConcurrentModificationException的异常。但这样做之后,其它线程对集合所做的add、remove或set修改就不会实时地在迭代中体现了。

所以,CopyOnWriteArrayList主要应用在读远多于写的场景,如缓存。


借鉴文章出处:http://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html

你可能感兴趣的:(Java基础)