zoukankan      html  css  js  c++  java
  • 并发编程【ArrayList、HashSet、HashMap线程不安全】

    一、前言

    在学习集合时,我们就已经知道了 ArrayList 是线程不安全的。

    那能不能通过代码来体现呢?

    因此,本文将演示 ArrayList 在多线程环境下的线程不安全的问题,并总结常用的解决办法。

    二、代码实现

    public class AboutList {
        public static void main(String[] args) {
            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();
            }
        }
    }
    

    当运行时,会出现如下问题:

    Exception in thread "24" java.util.ConcurrentModificationException
    	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
    	at java.util.ArrayList$Itr.next(ArrayList.java:861)
    	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
    	at java.lang.String.valueOf(String.java:2994)
    	at java.io.PrintStream.println(PrintStream.java:821)
    	at com.zuobiao.juc.AboutList.lambda$main$0(AboutList.java:17)
    	at java.lang.Thread.run(Thread.java:748)
    

    ConcurrentModificationException 就是常见的并发修改异常

    三、原因分析

    多个线程同时读和写,会造成数据不一致,进而报并发修改异常。

    补充:ArrayList 线程不安全还有其它情形,可以参考ArrayList 为什么线程不安全

    四、解决方案

    正因为 ArrayList 在多线程环境下是不安全的,那么我们只需要换一个线程安全的不就好了嘛。

    所以,我们就能通过如下三种方案来进行解决。

    (一)Vector

    ArrayList 改成 Vector 集合类:

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

    通过 Collections 工具类将 ArrayList 转换为线程安全的 ArrayList:

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

    使用并发编程类 CopyOnWriteArrayList 替换ArrayList:

    List<String> list = new CopyOnWriteArrayList();
    

    原理:写时复制

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

    CopyOnWriteArrayList 类的 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();
     }
    }
    

    推荐使用 CopyOnWriteArrayList 来进行解决。

    五、Set和Map

    同样,HashSet 和 HashMap 也是线程不安全的,因此也能用上述方法进行解决。

    (一)HashSet
    private static void setNotSafe() {
        Set<String> set = new HashSet<>();
        //Set<String> set = 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();
        }
    }
    
    (二)HashMap
    private static void mapNotSafe() {
        Map<String,String> map = new HashMap<>();
        //Map<String,String> map = new Hashtable<>();
        //Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
        //Map<String,String> map = new ConcurrentHashMap<>();
    
        for(int i=1; i<=300; i++){
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
    

    Java新手,若有错误,欢迎指正!

  • 相关阅读:
    Linux命令可以在后台运行,不随shell的关闭而关闭
    Linux系统创建python虚拟环境
    crontab定时任务不执行的一些原因总结
    解决ubuntu下定时任务不执行问题
    18-crm项目-kingadmin,完成crm用户认证登陆
    17-crm项目-kingadmin,前端展示数据库中不存在的字段
    16-crm项目-kingadmin,权限管理
    15-crm项目-kingadmin,自定义用户认证
    14-crm项目-kingadmin,动态url菜单优化
    记录安全问题---2020年9月24号
  • 原文地址:https://www.cnblogs.com/Java-biao/p/14497108.html
Copyright © 2011-2022 走看看