Java中的一些集合类:例如ArrayList、HashSet、HashMap 在并发条件下都是不安全的。一般情况下报的错误是:“Concurrent Modification Exception”
ArrayList
为什么ArrayList 不安全
在并发条件下,一个线程对ArrayList进行写入的时候,还没有写入结束,就被另一个线程抢过来写入就会造成数据不一致的异常,并发修改异常。
例如:A通过在纸上写字,B同学直接将纸抢过来,那么纸上就会有一道痕迹,这就造成了修改异常。
怎么解决ArrayList 不安全问题
一般情况下有三种解决方案:
- 使用Vector 类代替ArrayList。Vector 底层的add方法是加了 synchronized锁,虽然可以解决线程不安全问题,但是并发量就会下降,速度低。
- 使用Collections.synchronizedList(List)。使用Collections类,Collections在我看来可以解释为Collection的一些工具类,那么有线程不安全问题的话,Collections中有解决线程不安全的辅助方法。
- 使用CopyOnWriteArrayList<>()
上述CopyOnWriteArrayList 方法的原理是写时复制原则。add方法源码如下:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
从上述代码可以看出,在最外层加锁,ReentrantLock,然后内部使用的是写时复制原则:不是在原有的Object[] 上面进行写入,而是首先复制原有的Object[],然后在新的Object[] newElements中进行加入,然后将原有的 Object[] 引用指向新的 newElements。在写入的时候加锁,好处是:读的时候可以并发执行,写入的时候才会加锁。
对于Collections.synchronizedList的一些补充讲解:
可以看到当传入** ArrayList** 的时候,会返回 new SynchronizedRandomAccessList类。
SynchronizedRandomAccessList 和 SynchronizedList有什么区别呢?他们的主要区别就是是否实现了RandomAccess接口。如果实现了RandomAccess接口的话,那么一般使用for循环进行遍历,否则使用迭代器进行遍历。
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
可以看出 SynchronizedRandomAccessList类实现add 、remove等都是使用的父类的方法。下面代码中都是使用的 **synchronized (mutex) **进行上锁,而 mutex从源码中可以看出,它就是指的 **SynchronizedCollection ** 对象本身,所以如果使用 Collections.synchronizedList 来完成并发的效率会很低,因为它实现线程安全是对自己暴力加锁;
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
HashSet不安全问题
HashSet也是线程不安全的。同理,如果解决不安全问题可以使用:
- Collections.synchronizedSet();
- CopyOnWriteArraySet
但是从源代码中可以看出 CopyOnWriteArraySet 底层使用的是CopyOnWriteArrayList实现的。
private final CopyOnWriteArrayList<E> al;
public boolean add(E e) {
return al.addIfAbsent(e);
}
HashMap
HashMap是不安全的,解决方法有以下两种:
- 使用 Collections.synchronizedMap()
- 使用 ConcurrentHashMap 类。
ConcurrentHashMap 内部使用的是分段锁解决不安全问题。
ConcurrentHashMap和 HashMap一样内部都是数组+链表,分段锁就是对于数组的某一个元素进行加锁,那么最终的结果就是在 put 方法上面加锁,但是也不是完全的锁住,只在操作数组中某一元素的链表。
transient volatile Node<K,V>[] table;
final V putVal(K key, V value, boolean onlyIfAbsent) {
...
Node<K,V> f;
...
synchronized (f) {