Java进阶:Set、Map线程安全问题

前言

前面的博客已经分析了ArrayList线程不安全的原因,以及解决方案,感兴趣的可以参考这里:ArrayList线程安全问题详解
现在这边主要介绍的是Set、Map的线程安全问题

Set

Set最常用的就是HashSet类型,但是HashSet 是线程不安全的类型,不能用于并发环境。
如下这个例子,执行后会抛出java.util.ConcurrentModificationException的异常

    public static void main( String[] args ) throws InterruptedException {
    
    
        Set<String> set = new HashSet<>();
        for (int i = 1; i <= 30; i++){
    
    
            new Thread(() -> {
    
    
                set.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(set);
            }).start();
        };
    }

源码分析

    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();
    
    public boolean add(E e) {
    
    
        return map.put(e, PRESENT)==null;
    }
  • HashSet的底层是HashMap,Key就是我们添加的数据,Value是个常量Object对象
  • 既然如此,HashSet的线程不安全就是HashMap产生的,待会下面有介绍HashMap,这边先跳过

解决方案

  1. 使用CopyOnWriteArraySet初始化即可
        Set<String> set = new CopyOnWriteArraySet<>();
  1. 使用Collections.synchronizedSet初始化
        Set<String> set = Collections.synchronizedSet(new HashSet<>());

Map

Map最常用的就是HashMap类型,HashMap也是线程不安全的类型,也不能用于并发环境。
如下这个例子,执行后同样会抛出java.util.ConcurrentModificationException的异常

    public static void main( String[] args ) throws InterruptedException {
    
    
        Map<String, String> map = new HashMap<>();
        for (int i = 1; i <= 30; i++){
    
    
            new Thread(() -> {
    
    
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }).start();
        };
    }

异常原因

  • HashMap.put方法内部都是没有加锁的操作,所以并发操作很多属性都会乱掉。
  • 由于HashMap源码相对繁琐一点,所以这里不贴出来了,下一篇文章会专门进行HashMap源码解析

解决方案:采用线程安全的Map类

  • 使用ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
  • 使用Collections.synchronizedMap
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

猜你喜欢

转载自blog.csdn.net/qq_28834355/article/details/108664722