跳到主要内容
  1. 所有文章/
  2. Java并发编程笔记/

线程不安全的集合及解决方法

·📄 1131 字·🍵 3 分钟

List不安全 #

public class ListTest {
    public static void main(String[] args) {

        List<Object> arrayList = new ArrayList<>();

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

    }
}

/**
运行结果:
Exception in thread "4" java.util.ConcurrentModificationException  并发修改异常!
**/

产生原因:

迭代器是依赖于集合而存在的,在判断成功后,集合中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。 简单描述就是:迭代器遍历元素的时候,通过集合是不能修改元素的。

解决方案 #

推荐第三种!

  1. List<String> list=new Vector<>();
  2. List<String> list=Collections.synchronizedList(new ArrayList<>());
  3. List<String> list=new CopyOnWriteArrayList<>();

CopyOnWriteArrayList思想 #

写入时复制! COW(CopyOnWrite) 计算机程序设计领域的一种优化策略

核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList(关键!!因为写入时会复制一份新的)

多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;

CopyOnWriteArrayList比Vector厉害在哪里? #

Vector底层是使用synchronized关键字来实现的:效率特别低下。

image-20220209112844222.png

CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

image-20220209113109222.png

set 不安全 #

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

解决方案还是两种:

  • 使用Collections工具类的synchronized包装的Set类
  • 使用CopyOnWriteArraySet 写入复制的JUC解决方案
public class SetTest {
    public static void main(String[] args) {
        /**
         * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2. Set<String> set = new CopyOnWriteArraySet<>();
         */
//        Set<String> set = new HashSet<>();
        Set<String> set = new CopyOnWriteArraySet<>();

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

HashSet底层是什么? #

hashSet底层就是一个HashMap,为了让里面的元素不重复,使用了 Hashmap中的key来保证。

image-20220209120252977.png

image-20220209123421667.png

image-20220209123435648.png

Map不安全 #

public class MapTest {
    public static void main(String[] args) {
        //map 是这样用的吗?  不是,工作中不使用这个
        //默认等价什么? new HashMap<>(16,0.75);
        /**
         * 解决方案
         * 1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
         *  2. Map<String, String> map = new ConcurrentHashMap<>();
         */
        Map<String, String> map = new ConcurrentHashMap<>();
        //加载因子、初始化容量
        for (int i = 1; i < 100; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

两个重点 #

HashMap的底层原理,ConcurrentHashMap的底层原理