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碰撞

  • 相关阅读:
    概率论
    英语单词每日学习
    网上学习新课程--使用开发板动手进行uboot、内核以及驱动移植
    csdn专家主页
    material of DeepLearning
    I2C协议
    SVN appears to be part of a Subversion 问题心得
    @清晰掉 各种类型32位与64位下各类型长度对比
    超级方便的linux命令手册
    HTTP协议详解(转)
  • 原文地址:https://www.cnblogs.com/zblwyj/p/13426900.html
Copyright © 2011-2022 走看看