fail-safe 和 fail-fast 硬核解析,让你和面试官多聊十分钟!

简介: 简介java.util 包下的 属于 fail-fast , 快速失败~ 😝java.util.concurrent 包下的 属于 fail-safe ,安全失败~ 😝简单来说 就是 fail-fast 在迭代时,如果发现 该集合数据 结构被改变 (modCount != expectedModCount),就会 抛出 ConcurrentModificationException小伙伴们可以参考下 下面的代码简单实验一下~ 😋fail-fast 实验代码实验对象是 Hashtable,这里采用 jdk1.7 的写法 ~因为博主还在研究 下文

简介


java.util   包下的 属于  fail-fast    , 快速失败~ 😝


java.util.concurrent   包下的 属于  fail-safe   ,安全失败~ 😝


简单来说 就是    fail-fast   在迭代时,如果发现 该集合数据 结构被改变


(modCount != expectedModCount),就会 抛出


ConcurrentModificationException


小伙伴们可以参考下 下面的代码简单实验一下~ 😋


fail-fast 实验代码


实验对象是 Hashtable,这里采用 jdk1.7 的写法 ~


因为博主还在研究 下文中 ConcurrentHashMap 在7和8中有啥不一样 😝


class E implements Runnable{
    Hashtable<String, String> hashtable;
    public E(Hashtable<String, String> hashtable) {
        this.hashtable = hashtable;
    }
    private void add(Hashtable<String, String> hashtable){
        for (int i = 0; i < 10000000; i++) {
            hashtable.put("a",""+i);
        }
    }
    @Override
    public void run() {
        add(hashtable);
    }
}
public class D {
    public static void main(String[] args) {
        Hashtable<String, String> hashtable = new Hashtable<String, String>();
        hashtable.put("1","2");
        hashtable.put("2","2");
        hashtable.put("3","2");
        hashtable.put("4","2");
        hashtable.put("15","2");
        new Thread(new E(hashtable)).start();
        Set<Map.Entry<String, String>> entries = hashtable.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (iterator.hasNext()){
            System.out.println(iterator.next());
            iterator.remove();
        }
    }
}
复制代码


效果如图


网络异常,图片无法展示
|


触发的原理:


网络异常,图片无法展示
|


当集合数据结构发生变化时,这两个值是不相等的,所以会抛出该异常~ 。


结论:


虽然 HashTable  是 线程安全的  , 但是它有  fail-fast 机制  ,所以在多线程情况


下进行 迭代 也不能去修改它的数据结构!fail-fast 机制 不允许并发修改!


fail-safe  实验代码


class E implements Runnable{
    ConcurrentHashMap<String, String> concurrentHashMap;
    public E(ConcurrentHashMap<String, String> concurrentHashMap) {
        this.concurrentHashMap = concurrentHashMap;
    }
    private void add( ConcurrentHashMap<String, String> concurrentHashMap){
        for (int i = 0; i < 100000; i++) {
            concurrentHashMap.put("a"+i,""+i);
        }
    }
    @Override
    public void run() {
        add(concurrentHashMap);
    }
}
public class D {
    public static void main(String[] args) {
        ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<String, String>();
        concurrentHashMap.put("1","2");
        concurrentHashMap.put("2","2");
        concurrentHashMap.put("3","2");
        concurrentHashMap.put("4","2");
        concurrentHashMap.put("15","2");
        new Thread(new E(concurrentHashMap)).start();
        try {
            Thread.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Set<Map.Entry<String, String>> entries = concurrentHashMap.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry);
//            这里不用调用 iterator 去 remove
            concurrentHashMap.remove(entry.getKey());
        }
    }
}
复制代码


效果如图:


网络异常,图片无法展示
|


代码运行讲解,线程A 往里加数据,线程B 遍历它的数据,并删除。


可以看到这里并没有报错~,但是它也不能保证遍历到所有的值 (可以理解为无法获取到最新的值)


有没有感受到一丝丝 安全失败的感觉~ 😄


哈哈哈 它的特点就是  👉 允许并发修改,不会抛出


ConcurrentModificationException ,但是无法保证拿到的是最新的值


网络异常,图片无法展示
|


不知道小伙伴们看完上面的实验代码有没有疑惑


(・∀・(・∀・(・∀・*)


为什么可以调用它自身的 remove 呢?


别急~ 我们先来看看使用这个迭代器中发生了什么?


源码走起~


小伙伴们可以看看下面四张图~


创建迭代器的过程


网络异常,图片无法展示
|


网络异常,图片无法展示
|


网络异常,图片无法展示
|


网络异常,图片无法展示
|


图一 可以看到会去创造一个 EntryIterator , 而 它又 继承了 HashIterator ,在初始化时,会先调用父类的构造器。


图三可以发现  HashIterator  在初始化 时,会去调用 advance 方法 (这里就不展开这个 concurrentHashMap结构啦~ ) 这里的重点在最后一张图 , 它调用的是 UNSAFE.getObjectVolatile


它的作用是 强制从主存中获取属性值。


小伙伴们可以自行对比下 HashMap 或者 上面的 HashTable,他们都是直接 拿到代码中定义的这个 Entry[]~。🐷


网络异常,图片无法展示
|


不知道小伙伴们 get 得到这个点没有~


哈哈哈 容我唠叨唠叨一下~ 😝


4ye 在网上搜这个 fail-fast 和  fail-safe 的区别时,看到下面这张图。


几乎都在说 fail-safe 会复制原来的集合,然后在复制出来的集合上进行操作,然后


就说这样是不会抛出 ConcurrentModificationException 异常了。


可是这种说法是 不严谨的~ 😝  它描述的情况应该是针对这个  


CopyOnWriteArrayList  或者 CopyOnWriteArraySet 的情况(下面的源码讲到~)


网络异常,图片无法展示
|


CopyOnWriteArrayList  源码


可以发现这里 snapshot 的指针是始终指向这个原数组的(当你创建迭代器的时候)


网络异常,图片无法展示
|


当你添加数据时,它会复制原来的数组,并在复制出来的数组上进行修改,然后再设置


进去,可以发现至始至终都没有修改到这个原数组,所以迭代器中的数据是不受影响的~


😝


网络异常,图片无法展示
|


结论


fail-safe  也是得具体情况具体分析的。


  1. 如果是  CopyOnWriteArrayList  或者 CopyOnWriteArraySet  ,就属于 复制原来的集合,然后在复制出来的集合上进行操作 的情况 ,所以是不会抛出这个 ConcurrentModificationException  的 。


  1. 如果是这个 concurrentHashMap 的,就比较硬核了~ 😄 它直接操作底层,调用UNSAFE.getObjectVolatile  ,直接  强制从主存中获取属性值,也是不会抛出这个 ConcurrentModificationException  的 。


  1. 并发下,无法保证 遍历时拿到的是最新的值~




嘿嘿 现在回答上面那个 为啥可以 remove 的问题~


remove 的源码如下


网络异常,图片无法展示
|


网络异常,图片无法展示
|


网络异常,图片无法展示
|


重点在红框处, pred 为 null 表示是数组的头部,此时调用 setEntryAt ,这里也是出


现了这个  UNSAFE 😋 , setNext 也一样~


UNSAFE.putOrderedObject 这段代码的意思就是 :


有序的(有延迟的) 强制 更新数据到 主内存。(不能立刻被其他线程发现)


这些和 Java 的 JMM (Java内存模型)有关!    埋个坑🕳,后面写并发的时候更~ 😝



目录
相关文章
|
1月前
|
存储 缓存 NoSQL
Redis常见面试题全解析
Redis面试高频考点全解析:从过期删除、内存淘汰策略,到缓存雪崩、击穿、穿透及BigKey问题,深入原理与实战解决方案,助你轻松应对技术挑战,提升系统性能与稳定性。(238字)
|
3月前
|
存储 安全 测试技术
Python面试题精选及解析
本文详解Python面试中的六大道经典问题,涵盖列表与元组区别、深浅拷贝、`__new__`与`__init__`、GIL影响、协程原理及可变与不可变类型,助你提升逻辑思维与问题解决能力,全面备战Python技术面试。
139 0
|
21天前
|
监控 Java 关系型数据库
面试性能测试总被刷?学员真实遇到的高频问题全解析!
面试常被性能测试题难住?其实考的不是工具,而是分析思维。从脚本编写到瓶颈定位,企业更看重系统理解与实战能力。本文拆解高频面试题,揭示背后考察逻辑,并通过真实项目训练,帮你构建性能测试完整知识体系,实现从“会操作”到“能解决问题”的跨越。
|
5月前
|
Web App开发 缓存 前端开发
浏览器常见面试题目及详细答案解析
本文围绕浏览器常见面试题及答案展开,深入解析浏览器组成、内核、渲染机制与缓存等核心知识点。内容涵盖浏览器的主要组成部分(如用户界面、呈现引擎、JavaScript解释器等)、主流浏览器内核及其特点、从输入URL到页面呈现的全过程,以及CSS加载对渲染的影响等。结合实际应用场景,帮助读者全面掌握浏览器工作原理,为前端开发和面试提供扎实的知识储备。
236 4
|
5月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
2810 48
|
5月前
|
前端开发 JavaScript 开发者
2025 最新 100 道 CSS 面试题及答案解析续篇
本文整理了100道CSS面试题及其答案,涵盖CSS基础与进阶知识。内容包括CSS引入方式、盒模型、选择器优先级等核心知识点,并通过按钮、卡片、导航栏等组件封装实例,讲解单一职责原则、样式隔离、响应式设计等最佳实践。适合前端开发者巩固基础、备战面试或提升组件化开发能力。资源地址:[点击下载](https://pan.quark.cn/s/50438c9ee7c0)。
126 5
2025 最新 100 道 CSS 面试题及答案解析续篇
|
5月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
288 6
|
5月前
|
NoSQL Java 微服务
2025 年最新 Java 面试从基础到微服务实战指南全解析
《Java面试实战指南:高并发与微服务架构解析》 本文针对Java开发者提供2025版面试技术要点,涵盖高并发电商系统设计、微服务架构实现及性能优化方案。核心内容包括:1)基于Spring Cloud和云原生技术的系统架构设计;2)JWT认证、Seata分布式事务等核心模块代码实现;3)数据库查询优化与高并发处理方案,响应时间从500ms优化至80ms;4)微服务调用可靠性保障方案。文章通过实战案例展现Java最新技术栈(Java 17/Spring Boot 3.2)的应用.
381 9
|
5月前
|
缓存 算法 NoSQL
校招 Java 面试高频常见知识点深度解析与实战案例详细分享
《2025校招Java面试核心指南》总结了Java技术栈的最新考点,涵盖基础语法、并发编程和云原生技术三大维度: 现代Java特性:重点解析Java 17密封类、Record类型及响应式Stream API,通过电商案例演示函数式数据处理 并发革命:对比传统线程池与Java 21虚拟线程,详解Reactor模式在秒杀系统中的应用及背压机制 云原生实践:提供Spring Boot容器化部署方案,分析Spring WebFlux响应式编程和Redis Cluster缓存策略。
142 1
|
5月前
|
设计模式 安全 Java
Java 基础知识面试题全解析之技术方案与应用实例详解
本内容结合Java 8+新特性与实际场景,涵盖函数式编程、Stream API、模块化、并发工具等技术。通过Lambda表达式、Stream集合操作、Optional空值处理、CompletableFuture异步编程等完整示例代码,助你掌握现代Java应用开发。附面试题解析与技术方案,提升实战能力。代码示例涵盖计算器、员工信息统计、用户查询、模块化系统设计等,助你轻松应对技术挑战。
152 9

推荐镜像

更多
  • DNS