zoukankan      html  css  js  c++  java
  • 集合类线程安全问题

    涉及到的常用类

    • ArrayList
    • HashSet
    • HashMap

    在多线程下比较容易出现的异常是 java.util.ConcurrentModificationException,也就是并发修改异常。这个是由于并发争抢修改导致写入数据中断,数据出现异常

    案例演示

    List<String> list = new ArrayList<>();
            
    for (int i = 0; i < 30; i++) {
                
        new Thread(() -> {
                    
            list.add(UUID.randomUUID().toString().substring(0,6));
                    
            System.out.println(list);
                
        }, "thread" + i).start();
            
    }

     故障现象

     java.util.ConcurrentModificationException

    解决方案

     方案一:使用线程安全的list类

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

    方案二:使用集合工具类

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

    方案三:使用写时复制 CopyOnWriteArrayList

    List<String> list = new CopyOnWriteArrayList<>();

    优化建议

    采用方案三。

    理由:

    Vector和工具类都是使用了同步锁,效率低下,而CopyOnWriteArrayList则不同,可以看一下源码

    从源码可以看出,这里使用了可重入锁,其效率高于synchronized。其次,这里使用的是写时复制,是将当前的容器Object[ ] elements进行copy,其实底层就是数组的复制,复制出一个新的容器Object[ ] newElements,然后向新的容器中添加元素,添加完元素后,将原容器的引用指向新的容器,setArray(newElements)。这样做的好处是可以并发读而不需要加锁,因为当前的元素不需要添加元素,所有CopyOnWriteArrayList也是一种读写分离的思想,读和写是不同的容器。

    剩下的HashSet和HashMap也是类似的解决方案。例如:

    HashSet

        private static void setUnSafeCase() {
    //        Set<String> set = new HashSet<>();
    //        Set<String> set = Collections.synchronizedSet(new HashSet<>());
            Set<String> set = new CopyOnWriteArraySet<>();
            for (int i = 0; i < 30; i++) {
                new Thread(() -> {
                    set.add(UUID.randomUUID().toString().substring(0,6));
                    System.out.println(set);
                }, "thread" + i).start();
            }
        }

    CopyOnWriteArraySet 其实就是使用的CopyOnWriteArrayList

    HashMap

        private static void mapUnSafeCase() {
    //        Map<String, String> map = new HashMap<>();
    //        Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
            Map<String, String> map = new ConcurrentHashMap<>();
            for (int i = 0; i < 30; i++) {
                new Thread(() -> {
                    map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,6));
                    System.out.println(map);
                }, "thread" + i).start();
            }
        }

    这里的ConcurrentHashMap在1.7使用的分段锁,1.8又改成了红黑树,这里不再赘述,详见ConcurrentHashMap

  • 相关阅读:
    选择排序与冒泡排序
    判断是否为偶数
    mysql基础之mysql双主(主主)架构
    mysql基础之mysql主从架构半同步复制
    mysql基础之mysql主从架构
    mysql基础之数据库备份和恢复实操
    mysql基础之数据库备份和恢复的基础知识
    mysql基础之日志管理(查询日志、慢查询日志、错误日志、二进制日志、中继日志、事务日志)
    mysql基础之查询缓存、存储引擎
    mysql基础之数据库变量(参数)管理
  • 原文地址:https://www.cnblogs.com/weianlai/p/14589514.html
Copyright © 2011-2022 走看看