fail-safe 和 fail-fast 都是什么鬼?
点击关注公众号,Java干货及时送达
你真的了解 fail-fast
和 fail-safe
吗?👍
简介
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());
}
}
}
ConcurrentModificationException
,但是无法保证拿到的是最新的值为什么可以调用它自身的 remove
呢?
EntryIterator
, 而 它又 继承了 HashIterator
,在初始化时,会先调用父类的构造器。HashIterator
在初始化 时,会去调用 advance
方法 (这里就不展开这个 concurrentHashMap
结构啦~ ) 这里的重点在最后一张图 , 它调用的是 UNSAFE.getObjectVolatile
。HashMap
或者 上面的 HashTable
,他们都是直接 拿到代码中定义的这个 Entry[]
~。🐷fail-fast
和 fail-safe
的区别时,看到下面这张图。fail-safe
会复制原来的集合,然后在复制出来的集合上进行操作,然后就说这样是不会抛出 ConcurrentModificationException
异常了。CopyOnWriteArrayList
或者 CopyOnWriteArraySet
的情况(下面的源码讲到~)CopyOnWriteArrayList
源码snapshot
的指针是始终指向这个原数组的(当你创建迭代器的时候)结论
fail-safe
也是得具体情况具体分析的。如果是
CopyOnWriteArrayList
或者CopyOnWriteArraySet
,就属于 复制原来的集合,然后在复制出来的集合上进行操作 的情况 ,所以是不会抛出这个ConcurrentModificationException
的 。如果是这个
concurrentHashMap
的,就比较硬核了~ 😄 它直接操作底层,调用UNSAFE.getObjectVolatile
,直接 强制从主存中获取属性值,也是不会抛出这个ConcurrentModificationException
的 。并发下,无法保证 遍历时拿到的是最新的值~
remove
的问题啦~remove
的源码如下
setEntryAt
,这里也是出现了这个 UNSAFE 😋UNSAFE.putOrderedObject
这段代码的意思就是 :总结
java.util
包下的属于fail-fast
,ConcurrentModificationException
,modCount != expectedModCount
。😝java.util.concurrent
包下的 属于 fail-safe
,concurrentHashMap
获取和修改数据时 ,是通过 UNSAFE 类 直接从主内存中获取或者更新数据到主内存~ CopyOnWriteArrayList
或者 CopyOnWriteArraySet
,就直接 复制原来的集合,然后在复制出来的集合上进行操作 。关注Java技术栈看更多干货
评论