zoukankan      html  css  js  c++  java
  • Java中快速失败(fail-fast)和安全失败(fail-safe)的区别?

    快速失败(fail—fast):

      在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。

           原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

          注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

      场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

    安全失败(fail—safe):

      采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

          原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。

          缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

         场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

    那么平常我们如何去规避这种情况呢?

      这里有两种解决方案:

      方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList(不推荐)

      方案二:使用CopyOnWriteArrayList来替换ArrayList。

      CopyOnWriteArrayList为什么能解决这个问题呢?CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为了避免Copy出N个副本出来,导致并发写。但是。CopyOnWriteArrayList中的读方法是没有加锁的。

      我们只需要记住一句话,那就是CopyOnWriteArrayList是线程安全的,所以我们在多线程的环境下面需要去使用这个就可以了。关于CopyOnWriteArrayList更加深入的用法,会在以后的章节中去解释说明。

    fail-fast快速失败案例:

     1 public class FailFastDemo {
     2     private static List<String> list = new ArrayList<String>();
     3 
     4     public static void main(String[] args) {
     5         //两个线程对同一个ArrayList进行操作
     6         new ThreadOne().start();
     7         new ThreadTwo().start();
     8     }
     9     //输出list中的值
    10     private static void printAll(){
    11         try {
    12             String value = null;
    13             Iterator iterator = list.iterator();
    14             while (iterator.hasNext()){
    15                 value = (String) iterator.next();
    16                 System.out.println("list 中的值:"+value);
    17                 Thread.sleep(1000);
    18             }
    19         } catch (InterruptedException e) {
    20             e.printStackTrace();
    21         }
    22     }
    23     //线程一:向list中依次添加数据,然后printAll整个list
    24     private static class ThreadOne extends Thread{
    25         public void run() {
    26             for (int i = 0; i < 6; i++) {
    27                 list.add(String.valueOf("线程一:Java的架构师技术栈"+i));
    28                 printAll();
    29             }
    30         }
    31     }
    32     //线程二:对ArrayList进行同样的操作
    33     private static class ThreadTwo extends Thread{
    34         public void run() {
    35             for (int i = 0; i < 6; i++) {
    36                 list.add(String.valueOf("线程二:Java的架构师技术栈"+i));
    37                 printAll();
    38             }
    39         }
    40     }
    41 }

     更改后:使用CopyOnWriteArrayList替代ArrayList来避免异常发生

     1 public class FailFastDemo {
     2 //    private static List<String> list = new ArrayList<String>();
     3     //使用CopyOnWriteArrayList替代ArrayList
     4     private static List<String> list = new CopyOnWriteArrayList<String>();
     5     public static void main(String[] args) {
     6         //两个线程对同一个ArrayList进行操作
     7         new ThreadOne().start();
     8         new ThreadTwo().start();
     9     }
    10     //输出list中的值
    11     private static void printAll(){
    12         try {
    13             String value = null;
    14             Iterator iterator = list.iterator();
    15             while (iterator.hasNext()){
    16                 value = (String) iterator.next();
    17                 System.out.println("list 中的值:"+value);
    18                 Thread.sleep(1000);
    19             }
    20         } catch (InterruptedException e) {
    21             e.printStackTrace();
    22         }
    23     }
    24     //线程一:向list中依次添加数据,然后printAll整个list
    25     private static class ThreadOne extends Thread{
    26         public void run() {
    27             for (int i = 0; i < 6; i++) {
    28                 list.add(String.valueOf("线程一:Java的架构师技术栈"+i));
    29                 printAll();
    30             }
    31         }
    32     }
    33     //线程二:对ArrayList进行同样的操作
    34     private static class ThreadTwo extends Thread{
    35         public void run() {
    36             for (int i = 0; i < 6; i++) {
    37                 list.add(String.valueOf("线程二:Java的架构师技术栈"+i));
    38                 printAll();
    39             }
    40         }
    41     }
    42 }

    运行结果无异常:

  • 相关阅读:
    C程序设计语言阅读笔记
    Leetcode: . 存在重复元素 III
    Leetcode : 存在重复元素 II
    Leetcode : 二叉搜索树结点最小距离
    Leetcode: 二叉树的最大深度
    Leetcode: 二叉树的最小深度
    精益创业 埃里克莱斯 读书笔记总结--2020年的第12/100本
    经营最重要的事 梅纳德韦伯 读书笔记总结--2020年的第11/100本
    《创业36条军规 孙陶然》精读 读书笔记总结----《创业必读书第5本》---建立认知:有哪些优秀的创业经验 第3本
    原则 瑞达利欧 读书笔记总结--2020年的第9/100本
  • 原文地址:https://www.cnblogs.com/wk-missQ1/p/12767124.html
Copyright © 2011-2022 走看看