一. ArrayList在多线程下的完全隐患
面试官最喜欢问到的关于ArrayList在多线程下的三问是:
1. ArrayList是线程不安全的,请写一个不安全的例子出来?
2. 怎么解决呢?
3. 更好的解决方案呢?
1. ArrayList是线程不安全的,请写一个不安全的例子出来?
代码演示
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//开20个线程就稳稳报错了。
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
报错结果
解析
并发修改异常:
java.util.ConcurrentModificationException
导致异常原因:
并发争抢修改导致
例如:一个线程正在写,一个线程过来抢夺,导致数据不一致,导致并发修改异常。
2. 怎么解决呢?
1. List<String> list = new Vector<>();
使用Vector,因为他是线程安全的,但是这种解决方式很常见,
没有说到面试官心坎里。
2. List<String> list = Collections.synchronizedList(new ArrayList<>());
使用集合类中的方法,有点能力,但是还不够。
3. 更好的解决方案呢?
List<String> list = new CopyOnWriteArrayList<>();
写时复制,说出这个面试官才会满意的点点头,赞许的看着你,小伙子不错哦。
4.细说CopyOnWriteArrayList.add方法
CopyOnWrite容器即写时复制,往一个元素添加容器的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newElements,
让后新的容器添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements),
这样做可以对CopyOnWrite容器进行并发的读,而不需要加锁,
因为当前容器不会添加任何元素,
所以CopyOnWrite容器也是一种读写分离的思想,读和写在不同的容器中。
看一下源码就可以很好的理解上面文字的含义了
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#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();
}
}
二. HashSet与HashMap的解决方案
1.HashSet
HashSet的底层是HashMap:
但是调用add方法时,添加的是键(key),值是恒定的(Value)虚拟的。
他解决并发修改异常的方式也有二个:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
2.HashMap
他解决并发修改异常的方式也有二个:
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
//这里与前面不一样
Map<String, String> map = new ConcurrentHashMap<>();