你真的了解 fail-fast 和 fail-safe 吗
小伙伴们早上好呀~😝

本想着继续讲讲这个 的,突然挖掘到源码中的这些特点,应该对你面试时很有用!欢迎指正!😋

简介
包下的 属于 , 快速失败~ 😝
包下的 属于 ,安全失败~ 😝
简单来说 就是 在迭代时,如果发现 该集合数据 结构被改变 (),就会 抛出
小伙伴们可以参考下 下面的代码简单实验一下~ 😋
实验代码
实验对象是 ,这里采用 jdk1.7 的写法 ~
因为博主还在研究 下文中 在7和8中有啥不一样 😝
class E implements Runnable{
Hashtable hashtable;
public E(Hashtable hashtable) {
this.hashtable = hashtable;
}
private void add(Hashtable 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 hashtable = new Hashtable();
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> entries = hashtable.entrySet();
Iterator> iterator = entries.iterator();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (iterator.hasNext()){
System.out.println(iterator.next());
iterator.remove();
}
}
}
效果如图:

触发的原理:

当集合数据结构发生变化时,这两个值是不相等的,所以会抛出该异常~ 。
结论:
虽然 是 线程安全的 , 但是它有 机制 ,所以在多线程情况下进行 迭代 也不能去修改它的数据结构! 机制 不允许并发修改!
实验代码
class E implements Runnable{
ConcurrentHashMap concurrentHashMap;
public E(ConcurrentHashMap concurrentHashMap) {
this.concurrentHashMap = concurrentHashMap;
}
private void add( ConcurrentHashMap 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 concurrentHashMap = new ConcurrentHashMap();
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> entries = concurrentHashMap.entrySet();
for (Map.Entry entry : entries) {
System.out.println(entry);
// 这里不用调用 iterator 去 remove
concurrentHashMap.remove(entry.getKey());
}
}
}
效果如图:

代码运行讲解,线程A 往里加数据,线程B 遍历它的数据,并删除。
可以看到这里并没有报错~,但是它也不能保证遍历到所有的值 (可以理解为无法获取到最新的值)
有没有感受到一丝丝 安全失败的感觉~ 😄
哈哈哈 它的特点就是 👉 允许并发修改,不会抛出 ,但是无法保证拿到的是最新的值

不知道小伙伴们看完上面的实验代码有没有疑惑
(・∀・(・∀・(・∀・*)
为什么可以调用它自身的 呢?
别急~ 我们先来看看使用这个迭代器中发生了什么?
源码走起~
小伙伴们可以看看下面四张图~
创建迭代器的过程




从 图一 可以看到会去创造一个 , 而 它又 继承了 ,在初始化时,会先调用父类的构造器。
从 图三可以发现 在初始化 时,会去调用 方法 (这里就不展开这个 结构啦~ ) 这里的重点在最后一张图 , 它调用的是 。
它的作用是 强制从主存中获取属性值。
小伙伴们可以自行对比下 或者 上面的 ,他们都是直接 拿到代码中定义的这个 ~。🐷

不知道小伙伴们 get 得到这个点没有~
哈哈哈 容我唠叨唠叨一下~ 😝
4ye 在网上搜这个 和 的区别时,看到下面这张图。
几乎都在说 会复制原来的集合,然后在复制出来的集合上进行操作,然后就说这样是不会抛出 异常了。
可是这种说法是 不严谨的~ 😝 它描述的情况应该是针对这个 或者 的情况(下面的源码讲到~)

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

当你添加数据时,它会复制原来的数组,并在复制出来的数组上进行修改,然后再设置进去,可以发现至始至终都没有修改到这个原数组,所以迭代器中的数据是不受影响的~😝

结论
也是得具体情况具体分析的。
嘿嘿 现在回答上面那个 为啥可以 的问题~
的源码如下



重点在红框处, pred 为 null 表示是数组的头部,此时调用 ,这里也是出现了这个 UNSAFE 😋 , 也一样~
这段代码的意思就是 :
有序的(有延迟的) 强制 更新数据到 主内存。(不能立刻被其他线程发现)
这些和 Java 的 JMM (Java内存模型)有关! 埋个坑🕳,后面写并发的时候更~ 😝
总结
包下的属于 ,它的特点是
包下的 属于 ,它的特点是 允许并发修改,但是 无法保证在迭代过程中获取到最新的值 。 😋而且 修改数据时 是直接通过 UNSAFE 类 直接从主内存中获取数据,或者直接更新数据到主内存~ , 或者 ,就直接 复制原来的集合,然后在复制出来的集合上进行操作 ,是不同的操作~
最后
欢迎小伙伴们来一起探讨问题~
如果你觉得本篇文章还不错的话,那拜托再点点赞支持一下呀😝
让我们开始这一场意外的相遇吧!~
欢迎留言!谢谢支持!ヾ(≧▽≦*)o 冲冲冲!!
我是4ye 咱们下期应该……很快再见!! 😆
如果文章对您有所帮助,欢迎关注公众号 J a v a 4 y e 😆