集合类线程安全问题

涉及到的常用类

  • ArrayList
  • HashSet
  • HashMap

在多线程下比较容易出现的异常是 java.util.ConcurrentModificationException,也就是并发修改异常。这个是由于并发争抢修改导致写入数据中断,数据出现异常

案例演示

List<String> list = new ArrayList<>();
        
for (int i = 0; i < 30; i++) {
            
    new Thread(() -> {
                
        list.add(UUID.randomUUID().toString().substring(0,6));
                
        System.out.println(list);
            
    }, "thread" + i).start();
        
}

 故障现象

 java.util.ConcurrentModificationException

解决方案

 方案一:使用线程安全的list类

 List<String> list = new Vector<>();

方案二:使用集合工具类

List<String> list = Collections.synchronizedList(new ArrayList<>());

方案三:使用写时复制 CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();

优化建议

采用方案三。

理由:

Vector和工具类都是使用了同步锁,效率低下,而CopyOnWriteArrayList则不同,可以看一下源码

从源码可以看出,这里使用了可重入锁,其效率高于synchronized。其次,这里使用的是写时复制,是将当前的容器Object[ ] elements进行copy,其实底层就是数组的复制,复制出一个新的容器Object[ ] newElements,然后向新的容器中添加元素,添加完元素后,将原容器的引用指向新的容器,setArray(newElements)。这样做的好处是可以并发读而不需要加锁,因为当前的元素不需要添加元素,所有CopyOnWriteArrayList也是一种读写分离的思想,读和写是不同的容器。

剩下的HashSet和HashMap也是类似的解决方案。例如:

HashSet

    private static void setUnSafeCase() {
//        Set<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,6));
                System.out.println(set);
            }, "thread" + i).start();
        }
    }

CopyOnWriteArraySet 其实就是使用的CopyOnWriteArrayList

HashMap

    private static void mapUnSafeCase() {
//        Map<String, String> map = new HashMap<>();
//        Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,6));
                System.out.println(map);
            }, "thread" + i).start();
        }
    }

这里的ConcurrentHashMap在1.7使用的分段锁,1.8又改成了红黑树,这里不再赘述,详见ConcurrentHashMap

原文地址:https://www.cnblogs.com/weianlai/p/14589514.html