第五章:(2)HashSet 集合线程不安全&解决方案


一、HasheSet 集合的不安全

  1、故障现象

public class NotSafeSetDemo {
    public static void main(String[] args) {
        Set set = new HashSet<>();

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                //向集合添加内容
                set.add(UUID.randomUUID().toString().substring(0, 8));
                //从集合获取内容
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

  运行结果:

  

  2、分析

  (1)HashSet 是线程不安全的   (2)HashSet 的底层是一个 HashMap(),但是这个 HashMap 的 value 永远是一个 Object 类型的常量(PRESENT),但HashSet的add是放一个值(调用map的PUT方法),而HashMap是放K、V键值对
public HashSet() {
    map = new HashMap<>();
}
 
private static final Object PRESENT = new Object();
 
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

    因为底层使用的是 HashMap,而 HashMap 也是线程不安全的,并且 HashSet 的 add 方法也没有使用 synchronized 同步锁。

二、方案一

  1、使用 Collections 工具类

  Collections 工具类也提供了线程安全的 Set 集合。

  2、代码实现

public class SafeSetDemo {

    public static void main(String[] args) {
        Set set = Collections.synchronizedSet(new HashSet<>());

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                //向集合添加内容
                set.add(UUID.randomUUID().toString().substring(0, 8));
                //从集合获取内容
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

三、方案二

  1、使用 CopyOnWriteArraySet 

    原理同 CopyOnWriteArrayList。

  2、代码实现

public class SafeSetDemo {

    public static void main(String[] args) {

        Set set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                //向集合添加内容
                set.add(UUID.randomUUID().toString().substring(0, 8));
                //从集合获取内容
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}
JUC