Loading...

集合类线程不安全问题

学习地址:https://www.bilibili.com/video/BV18b411M7xz?p=20

问题现象

从ArrayList入手

public static void main(String[] args) {
        testArrayList();
//        testHashSet();
//        testHashMap();
}
public static void testArrayList() {
    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();
    }
}

报错java.util.ConcurrentModificationException

导致原因

线程并发争抢修改导致,参考给花名册签名情况。

一个人正在写入,另一个同学过来抢夺,导致数据不一致异常。并发修改异常。

解决方案

  1. new Vector<>();

    • JDK1.0就存在,ArrayList是JDK1.2出现

    • //查看源码得知,Vector是通过加sync实现线程安全,降低了效率
      public synchronized boolean add(E e) {
          modCount++;
          ensureCapacityHelper(elementCount + 1);
          elementData[elementCount++] = e;
          return true;
      }
      
  2. Collections.synchronizedList(new ArrayList<>());

    • 使用集合的包装方法

    • //查看源码得知,Collections提供了sync的方法,实现线程安全
      public static <T> List<T> synchronizedList(List<T> list) {
          return (list instanceof RandomAccess ?
                  new SynchronizedRandomAccessList<>(list) :
                  new SynchronizedList<>(list));
      }
      
  3. new CopyOnWriteArrayList<>()

    • 实现了读写分离,即实现了添加,又将读操作和写操作分离开,不影响效率

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

补充HashSet、HashMap

public static void testHashSet() {
    Set<String> set1 = new HashSet<>();
    Set<String> set2 = 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();
    }
    new HashSet<>();//底层HashMap<>();
    /**
         * HashMap<>()是k-v键值对,HashSet实现时只保留key值,value是PRESENT  一个常量
         * public boolean add(E e) {
         *      return map.put(e, PRESENT)==null;
         * }
         */
    new HashSet<>().add("a");//
}
public static void testHashMap() {
    Map<String,String> map1 = new HashMap<>();
    Map<String,String> map2 = Collections.synchronizedMap(new HashMap<>());
    //注意HashMap使用new ConcurrentHashMap<>()
    Map<String,String> map = new ConcurrentHashMap<>();
    for (int i = 1; i <= 30; i++) {
        new Thread(() -> {
            map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));
            System.out.println(map);
        }, String.valueOf(i)).start();
    }
}

优化建议

ArrayList、HashSet推荐使用new CopyOnWriteArrayXXX();

HashMap推荐使用new ConcurrentHashMap<>();

posted @ 2020-10-12 11:39  iniwym  阅读(103)  评论(0编辑  收藏  举报