fail-safe 和 fail-fast 都是什么鬼?

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  的 。

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

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


  • 10
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值