Java SE之快速失败(Fast-Fail)与快速安全(Fast-Safe)的区别[集合与多线程/增强For](彻底详解)
声明
特点:基于JDK源码进行分析。
研究费时费力,如需转载或摘要,请显著处注明出处,以尊重劳动研究成果:博客园 - https://www.cnblogs.com/johnnyzen/p/10547179.html,侵权必究,蟹蟹理解。
/**
* @IDE: Created by IntelliJ IDEA.
* @Author: 千千寰宇
* @Email: 1125418540@qq.com
* @Date: 2019/3/17 12:46:34
* @Description: Java SE之Fast-Fail与Fast-Safe的区别[集合与多线程/增强For]
*/
[1] Fast-Fail事件
特征
① java.util包下面的所有的集合类
+ ArrayList / HashMap / LinkedList / Iterator / Collections / …
②会抛出ConcurrentModificationException异常,一种错误检测机制
③产生条件:其一,当多个线程对Collection下的集合类进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;其二,单线程环境下,如果单次遍历对该包下的集合对象进行多次写操作,则也可能抛出ConcurrentModificationException异常。
④解决办法:通过util.concurrent包下的相应类去处理,则:不会产生fast-fail事件,即fast-safe
案例分析
【测试源码】
public static void arrayList(){
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
for(Object obj : list){
System.out.println(obj);
list.remove(obj); //使用集合对象list删除元素
}
}
【运行结果】
【原因】
//ArrayList的内部数组
transient Object[] elementData;
//ArrayList的iterator()方法
public Iterator<E> iterator() {
return new Itr();
}
//ArrayList的remove()方法
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index); //关键调用处:修改 modCount++
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);//关键调用处:修改 modCount++
return true;
}
}
return false;
}
//ArrayList的fastRemove()方法
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//对list对象内部数组的元素进行删除,但会修改modCount值
elementData[--size] = null; // clear to let GC do its work
} //ArrayList 的私有内部类Itr implements Iterator的next()
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//检查modCount与expectedModCount
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() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
//对list对象内部数组的元素进行删除,但不会修改modCount值
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;//即 使用ArrayList对象获取的iterator对象的remove方法删除元素时,将设置expectedModCount = modCount ,以确保下一次能通过checkForComodification()
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}//Itr.remove() end
}//Itr end
1.Java.util包下的集合类(如:ArrayList)的迭代器(如:私有内部类Itr)在遍历时直接访问集合中的内容,并且在遍历过程中使用了modCount变量
2.Java.util包下的集合类(如:ArrayList)的集合对象中在被遍历期间如果内容发生变化,就会改变modCount的值(如:ArrayList的remove())
3.每当集合类(如:ArrayList)对象的迭代器使用 hashNext()/next()遍历下一个元素之前,都会检测modCount变量和expectedmodCount值是否相等
4.如果相等就返回遍历结果,否则抛出异常,终止遍历
(整个分析过程,可参见上述ArrayList中源码)引用于[2]
【另外:关于ArrayList.remove()与其ArrayList.iterator().reomve()的区别?】
上述案例的源码中使用 Collection(ArrayList 的父类) 集合对象list中的 remove()方法。该方法只能从集合中删除元素,不能把迭代器iterator中的元素也删除了。即:
ArrayList.remove() 删除元素时,修改modCount++ |
ArrayList.Itr.remove() 删除元素时,调用ArrayList.remove()后,立即设置modCount=expectedmodCount, 避免了并发修改异常(ConcurrentModificationException)。 值得注意的是:两方法均是对同一对象的同一动态数组(elementData)进行操作。那么,在多线程环境下,如果多个线程对同一list对象进行操作时,elementData便是实际上的共享互斥资源,则 可能造成数据污染的非线程安全事件。 |
【产生问题】
1.【单线程环境】当使用foreach循环时,欲实现对util包下的集合对象的一边遍历一边增、删集合元素时,该怎么办?
答:其一,每次遍历进行单次写操作,通过集合对象的迭代器iterator对应增、删元素(Itr.remove())的方法,线程安全。
//demo 不会抛并发修改异常
Iterator it = list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
if(obj.equals(1)){
it.remove();//使用迭代器的remove()方法
}
}
其二,每次遍历进行多次写操作,不一定线程安全。原因是:并发修改异常触发时,必须满足checkForComodification()方法的modCount == expectedmodCount的成立。那么,如果集合发生变化时修改modCount值, 刚好有设置为了expectedmodCount值, 则异常不会抛出。
比如:先删除一条数据,再添加一条数据。
//此时,不会抛并发修改异常
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
for(Object obj : list){
System.out.println(obj);
if(obj == new Integer(2)){ //使用equals()也会抛并发修改异常
list.remove(obj);
list.add(2);
}
}
2.【多线程环境】当多个线程同时对util包下的同一集合对象进行写操作时,是否线程安全?
答:不能依赖于ConcurrentModificationException异常是否抛出,而进行并发操作的编程, 该异常只检测并发修改的BUG。
【结论】
java.util包下的集合类都是快速失败(fail-fast)机制的, 不能在多线程下发生并发修改,在单线程下,也不推荐迭代过程中修改该集合类对象的元素值。引用于[2]
[2] Fast-Safe事件
①java.util.concurrent包下面的所有的类
+ BlockingDeque / LinkedBlockingDeque / SynchronousQueue
+ ConcurrentHashMap / ConcurrentLinkedQueue / CopyOnWriteArrayList
+ …
②不会抛出并发修改异常;采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
③结论:java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。 引用于[2]
④原理:由于迭代时是对原集合的拷贝的值进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会出发ConcurrentModificationExceptio
⑤缺陷:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地, 迭代器并不能访问到修改后的内容 (简单来说就是, 迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的) 引用于[2]
[3] 补充:增强for
特征
①增强型for | For/in | Foreach循环语法:for(ElementType element:arrayName){}
②不支持遍历时修改。详见上述fast-fail中关于“每次遍历单/多次修改”。
③对遍历的集合需要做null判断,不然可能引发空指针异常。
④增强for循环的内部:
遍历数组,foreach 循环实际上还是用的普通的 for 循环 引用[1]
遍历集合,foreach 循环实际上是用的 iterator 迭代器迭代 引用[1]
备注:始于JDK1.5
案例分析
【测试源码】
//实验
public class Foreach {
public static void foreach(){
int [] array = {1,2,3};
for(int i : array){
System.out.println(i);
}
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
for(Object obj : list){
System.out.println(obj);
}
}
}
【反编译结果】javap -c sourceFile
[4] 参考文献
备注:该大V博文关于fast-fail与fast-safe的探究,认为是增强for循环基于多线程是错误的,毫无依据,JDK源码中也并未体现。
[2]【荐】fail-fast和fail-safe的介绍和区别
[4]【荐】Concurrent下的线程安全集合
Java SE之快速失败(Fast-Fail)与快速安全(Fast-Safe)的区别[集合与多线程/增强For](彻底详解)的更多相关文章
- 快速失败(fail—fast)和 安全失败(fail—safe)
快速失败(fail-fast) 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加.删除),则会抛出Concurrent Modification Exception. 原理 ...
- Java集合框架中的快速失败(fail—fast)机制
fail-fast机制,即快速失败机制,是java集合框架中的一种错误检测机制.多线程下用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加.删除),则会抛出Concurre ...
- Java集合【2】--iterator接口详解
目录 一.iterator接口介绍 二.为什么需要iterator接口 三.iterator接口相关接口 3.1 ListIterator 3.2 SpitIterator 3.2.1 SpitIte ...
- Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置
一个例子 public class TestString{ public static void main(String[] args){ String a = "a"; Stri ...
- Java多线程之线程池详解
前言 在认识线程池之前,我们需要使用线程就去创建一个线程,但是我们会发现有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因 ...
- java集合HashMap、HashTable、HashSet详解
一.Set和Map关系 Set代表集合元素无序,集合元素不可重复的集合,Map代表一种由多个key-value组成的集合,map集合是set集合的扩展只是名称不同,对应如下 二.HashMap的工作原 ...
- java Spring系列之 配置文件的操作 +Bean的生命周期+不同数据类型的注入简析+注入的原理详解+配置文件中不同标签体的使用方式
Spring系列之 配置文件的操作 写在文章前面: 本文带大家掌握Spring配置文件的基础操作以及带领大家理清依赖注入的概念,本文涉及内容广泛,如果各位读者耐心看完,应该会对自身有一个提升 Spri ...
- java多线程——同步块synchronized详解
Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(synchronzied) 实例方法同步 静 ...
- Java基础学习(四)-- 接口、集合框架、Collection、泛型详解
接口 一.接口的基本概念 关键字为:Interface,在JAVA编程语言中是一个抽象类型,是抽象方法的集合.也是使用.java文件编写. 二.接口声明 命名规范:与类名的命名规范相同,通常情况下 ...
随机推荐
- Windows下查看硬连接引用技术
Win10有了bash,可以方便的进入并用ll查看文件的硬连接数. 但是用powershell直接查看就比较麻烦了,比较曲折的找到了方法: fsutil hardlink list [filename ...
- Docker: Jenkins里的pipeline编写基本技巧
Jenkins里,先新建一个pipeline项目 Pipeline Syntax 在Sample Step里选择需要的插件,如果不存在,就去系统管理,插件管理里,进行安装. 如果源码管理工具用的是gi ...
- Parameter 'ids' not found. Available parameters are [array]
传的参数是一个数组, Long[] ids 后台错误写法 <delete id="deleteById"> delete from table where id in ...
- 51Nod 1004 n^n的末位数字
思路:首先将0~9的平方的尾数放在一个数组a里面,方便后面直接调用,因为不论多大的数做什么运算,得到的结果的最后一位数只和运算前所有数的最后一位数有关系.新建变量d,z一个是底数,一个是幂次.循环判断 ...
- 课堂练习6--统计txt文本
统计文本中26个字母的频率: package bao; import java.io.BufferedReader; import java.io.FileReader; import java.io ...
- 彻底搞懂spark的shuffle过程(shuffle write)
什么时候需要 shuffle writer 假如我们有个 spark job 依赖关系如下 我们抽象出来其中的rdd和依赖关系: E <-------n------, ...
- 通知实战 设置通知图片(iOS10以后的)
解释两个基本扩展(Notification Content.Notification Service) Notification Content其实是用来自定义长按通知显示通知的自定义界面 Notif ...
- 【学习总结】win7使用anaconda安装tensorflow+keras
tips: Keras是一个高层神经网络API(高层意味着会引用封装好的的底层) Keras由纯Python编写而成并基Tensorflow.Theano以及CNTK后端. 故先安装TensorFlo ...
- Windows elasticsearch1.5.1安装
http.cors.enabled: true http.cors.allow-origin: /.*/ network.host: 192.168.2.200 http.port: cluster. ...
- TCP/IP协议、UDP协议、 Http协议
开放式系统互联通信参考模型(Open System Interconnection Reference Model,缩写为 OSI),简称为OSI模型(OSI model),一种概念模型,由国际标准化 ...