并发编程【ArrayList、HashSet、HashMap线程不安全】

一、前言

在学习集合时,我们就已经知道了 ArrayList 是线程不安全的。

那能不能通过代码来体现呢?

因此,本文将演示 ArrayList 在多线程环境下的线程不安全的问题,并总结常用的解决办法。

二、代码实现

public class AboutList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        for(int i=1; i<=30; i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

当运行时,会出现如下问题:

Exception in thread "24" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
	at java.util.ArrayList$Itr.next(ArrayList.java:861)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.zuobiao.juc.AboutList.lambda$main$0(AboutList.java:17)
	at java.lang.Thread.run(Thread.java:748)

ConcurrentModificationException 就是常见的并发修改异常

三、原因分析

多个线程同时读和写,会造成数据不一致,进而报并发修改异常。

补充:ArrayList 线程不安全还有其它情形,可以参考ArrayList 为什么线程不安全

四、解决方案

正因为 ArrayList 在多线程环境下是不安全的,那么我们只需要换一个线程安全的不就好了嘛。

所以,我们就能通过如下三种方案来进行解决。

(一)Vector

ArrayList 改成 Vector 集合类:

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

通过 Collections 工具类将 ArrayList 转换为线程安全的 ArrayList:

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

使用并发编程类 CopyOnWriteArrayList 替换ArrayList:

List<String> list = new CopyOnWriteArrayList();

原理:写时复制

CopyOnWrite 容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器 Object[] 添加,而是现将当前容器 Object[] 进行Copy,复制出一个新的容器 Object[] newElements,然后新的容器 Object[] newElements 里添加元素,添加完元素之后,再将原容器的引用指向新的容setArray(newElements)。这样做的好处是可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopyOnWrite 容器也是一种读写分离的思想,读和写不同的容器。

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();
 }
}

推荐使用 CopyOnWriteArrayList 来进行解决。

五、Set和Map

同样,HashSet 和 HashMap 也是线程不安全的,因此也能用上述方法进行解决。

(一)HashSet
private static void setNotSafe() {
    Set<String> set = new HashSet<>();
    //Set<String> set = Collections.synchronizedSet(new HashSet<>());
    //Set<String> set = new CopyOnWriteArraySet<>();

    for(int i=1; i<=30; i++){
        new Thread(()->{
            set.add(UUID.randomUUID().toString().substring(0,8));
            System.out.println(set);
        }, String.valueOf(i)).start();
    }
}
(二)HashMap
private static void mapNotSafe() {
    Map<String,String> map = new HashMap<>();
    //Map<String,String> map = new Hashtable<>();
    //Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
    //Map<String,String> map = new ConcurrentHashMap<>();

    for(int i=1; i<=300; i++){
        new Thread(()->{
            map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
            System.out.println(map);
        }, String.valueOf(i)).start();
    }
}

Java新手,若有错误,欢迎指正!

posted @ 2021-03-07 22:45  跑调大叔!  阅读(220)  评论(0编辑  收藏  举报