Java集合如何保证线程安全?Fail-fast、Fail-safe机制是什么?
有个学弟在面快手的时候,被问到Java非线程安全的集合,如HashMap,ArrayList是如何保证线程安全的时候犯了难,也没想到会问这么细,这篇文章将很快的说清楚这里的逻辑,发车~
一些源码
首先我们看看HashMap的一些关键代码:
我们注意到会有一个ModCount(修改计数)的属性,简而言之,容器在修改的时候会检查这个modCount是否与自己当前应该的modCount是否相同,不同的话就会快速失败,抛出
ConcurrentModificationException
,详情看下面的代码:
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
看这个方法就很清楚了,其他非线程安全集合如ArrayList也是这么处理的,ArrayList中直接封装了一个判断的方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Fail-fast
望文生义,fail-fast快速失败机制,是多线程并发操作集合时的一种失败处理机制。
在集合遍历过程中,一旦发现容器中的数据被修改了,会立刻抛出ConcurrentModificationException异常,从而导致遍历失败。
这里如果我一边遍历集合,一边添加元素,是会报ConcurrentModificationException的。
Fail-safe
Fail-safe:表示失败安全,也就是在这种机制下,出现集合元素的修改,不会抛出ConcurrentModificationException
。
原因是采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,
在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到
比如这种情况
public class Test {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{1,7,9,11});
Iterator itr = list.iterator();
while(itr.hasNext()) {
Integer i = (Integer) itr.next();
System.out.println(i); //是不会打印15的
if(i==7){
list.add(15);
}
}
}
}
定义了一个CopyOnWriteArrayList,在对这个集合遍历过程中,对集合元素做修改后,不会抛出异常,但同时也不会打印出增加的元素。
java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。
常见的的使用Fail-safe方式遍历的容器有ConcerrentHashMap和CopyOnWriteArrayList等。
这里还会涉及到一个加锁操作
这里就会有一个问题,如果我有多个线程副本,最后合并的时候会不会有问题?
cowArrayList 修改操作有锁保护,一个线程想修改,得
1.先拿锁
2.copy出一个新数组
3.修改元素
4.setArray(修改cowArrayList 引用为这个新数组)
- 释放锁
源码大概长这样:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 1. 加锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 2. copy得到一个新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 3. 添加元素到新数组
newElements[len] = e;
// 4. 修改数组的引用为新数组
setArray(newElements);
return true;
} finally {
// 5. 释放锁
lock.unlock();
}
}
转载自:https://juejin.cn/post/7280435532987088951