zoukankan      html  css  js  c++  java
  • JAVA集合框架中线程安全问题

    1、ArraryList相关

    ArrayList是线程不安全的,在多线程下同时操作一个集合会出java.util.ConcurrentModificationException异常(并发修改异常),如下所示:

        public static void main(String[] args) throws IOException {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 30; i++) {
                new Thread(() -> {
                    list.add(UUID.randomUUID().toString().substring(0, 8));
                    System.out.println(list);
                }, "thread" + i).start();
            }
        }

      解决办法:① 、使用List<String> list = new Vector<>();

           ②、 使用List<String> list = Collections.synchronizedList(new ArrayList<>());

           ③、 使用List<String> list = new CopyOnWriteArrayList<>();

    Vector线程安全原因:

        /**
         * Appends the specified element to the end of this Vector.
         *
         * @param e element to be appended to this Vector
         * @return {@code true} (as specified by {@link Collection#add})
         * @since 1.2
         */
        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }

    CopyOnWriteArrayList线程安全原因:

    /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return {@code true} (as specified by {@link Collection#add})
         */
        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();
            }
        }

    ReentrantLock和synchronized 区别:https://baijiahao.baidu.com/s?id=1648624077736116382&wfr=spider&for=pc

     ArrayList生僻知识点:

    ①、创建时可以自己指定长度,不指定默认长度为10,每次扩容以当前长度0.5倍进行扩容,第一次10,第二次15

    ②、ArrayList底层是数组结构,所以不能通过forEache和增强for循环去进行删除(迭代器方式)和新增

    ③、可以通过正常for循环去操作for(int i=0;i<list.size();i++),虽然此方法不报错,但是不推荐(涉及到数组下标移动问题)

    ④、可通过临时集合将待删除数据记录,待循环结束后,使用removeAll()一起删除

    提示:Vectore初始默认长度也为10,但是每次扩容都是按1倍进行扩容,第一次10,第二次20

    2、HashSet相关

     HashSet为线程不安全的,在多线程下同时操作一个集合也会出java.util.ConcurrentModificationException异常(并发修改异常),实例如下:

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

    解决办法:

    ①、使用Set<String> list = Collections.synchronizedSet(new HashSet<>());

    ②、使用Set<String> list = new CopyOnWriteArraySet<>();

    CopyOnWriteArraySet的底层依旧是采用和CopyOnWriteArrayList,如下所示:

         /**
         * Creates an empty set
         */
        public CopyOnWriteArraySet() {
            al = new CopyOnWriteArrayList<E>();
        }

    HashSet生僻知识点:

    ①、HashSet底层是通过HashMap进行实现的,故此HashMap也会有线程安全问题

    ②、HashSet的add方法底层就是调用的HashMap的put方法,只不过value全部都是一个常量对象private static final Object PRESENT = new Object();

    ③、HashSet的元素全部都是作为HashMap的key值,所以自定义HashSet元素相等方法时必须要重写HashCode方法

    3、HashMap相关

    HashMap跟HashSet一样,因为HashSet底层就是采用HashMap,所以也会有线程安全的问题

    解决办法:

    ①、使用Map<String, Object> objectObjectMap = Collections.synchronizedMap(new HashMap<>());

    ②、使用Map<Object, Object> objectObjectConcurrentHashMap = new ConcurrentHashMap<>();

    ③、使用Map<Object, Object> hashtable = new Hashtable<>();

    Hashtable和Vector相似,都是jdk1.0的产物,都是采用了synchronized关键字进行枷锁,所以都是线程安全,但是效率稍微低一点

    ConcurrentHashMap则是采用对链表进行分段加锁实现现在安全,效率比HashTable高很多,推荐使用

    HashMap生僻知识点:

    ①、HashMap底层是数组+链表结构实现

    ②、创建一个HashMap是默认数组长度为16(必须是2的幂),每次扩容时以之前的2倍进行扩容,第一次16,第二次48

    ③、HashMap的初始容量可以自己指定,一版要求都是以2的n次幂的形式存在的

    结论:

    HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!

    为什么HashMap的容量是2的n次幂?

     HashMap的容量为什么是2的n次幂,和这个(n - 1) & hash的计算方法有着千丝万缕的关系,符号&是按位与的计算,这是位运算,计算机能直接运算,特别高效,按位与&的计算方法是,只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞

  • 相关阅读:
    django页面分类和继承
    django前端从数据库获取请求参数
    pycharm配置django工程
    django 应用各个py文件代码
    CF. 1428G2. Lucky Numbers(背包DP 二进制优化 贪心)
    HDU. 6566. The Hanged Man(树形背包DP DFS序 重链剖分)
    小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)
    区间树 学习笔记
    CF GYM. 102861M. Machine Gun(主席树)
    2016-2017 ACM-ICPC East Central North America Regional Contest (ECNA 2016) (B, D, G, H)
  • 原文地址:https://www.cnblogs.com/zblwyj/p/13426900.html
Copyright © 2011-2022 走看看