zoukankan      html  css  js  c++  java
  • juc学习四(集合不安全问题)

    并发环境下,我们经常使用的集合类(List、Map、Set)其实都是不安全的。

    集合不安全问题之List

    List在单线程的情况下是安全的,但是多线程的情况下是不安全的,我们来看两段代码:

    单线程

    public class UnsafeList1 {
    
        public static void main(String[] args) {
            List<String> list= Arrays.asList("a","b","c");
            list.forEach(System.out::println);
        }
    }
    

     

     多线程

    public class UnsafeList2 {
        public static void main(String[] args) {
    
            List<String> list=new ArrayList<>();
            for (int i = 1; i <=50; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    

     通过以上两段代码,我们可以看到,在多线程情况下会报java.util.ConcurrentModificationException并发修改异常。

    而ArrayList是jdk1.2出现的,是一个不加锁的集合类,并发性上升,但是牺牲多线程的安全性为代价再看ArrayList源码发现add方法没有加锁,所有多线程情况下出现线程异常问题。

     解决方案

    1 使用List接口下的实现集合Vector 类,但是该类是JDK1.0出现的,其add()是syncronized修饰的。数据一致性可以得到保证,但是并发性会下降。

    .

     2 使用工具类 Collections中的synchronizedList将其变为安全的集合类,其中还有构建set,map集合安全类的方法。

    public class UnsafeList4 {
        public static void main(String[] args) {
            //构建一个同步的synchronizedList
            List<String> list= Collections.synchronizedList(new ArrayList<>());
            for (int i = 1; i <=50; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    

     3 JUC包中的CopyOnWriteArrayList,推荐使用

    public class UnsafeList5 {
        public static void main(String[] args) {
         //写时复制容器
            List<String> list= new CopyOnWriteArrayList<>();
            for (int i = 1; i <=50; i++) {
                new Thread(()->{
                    list.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(list);
                },String.valueOf(i)).start();
            }
        }
    }
    

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

    CopyOnWriteArrayList的add源码,底层volatile Object[] array,add方法里面加了锁ReentrantLock

     写入时复制(COW)思想原理:指针,复制指向的问题

    集合的线程不安全问题之Set 

    HashSet底层数据结构是HashMap(源码构造器里面 new HashMap()),其add方法实际上return map.put(e,PRESENT)==null; PRESENT实际上就是一个object常量,所以实际上就是HashMap的keys。

     

     

    解决方案

    1 使用工具类 Collections => Set<Object> set = Collections.synchronizedSet(new HashSet<>());
    2 JUC包 中的Set<Object> set = new CopyOnWriteArraySet<>();

    CopyOnWriteArraySet和CopyOnWriteArrayList类似,其实CopyOnWriteSet底层包含一个CopyOnWriteList,几乎所有操作都是借助CopyOnWriteList实现的,就像HashSet包含HashMap。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过通过动态数组实现的“集合”! CopyOnWriteArrayList中允许有重复的元素;但是,CopyOnWriteArraySet是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!至于CopyOnWriteArraySet的“线程安全”机制,和CopyOnWriteArrayList一样,是通过volatile和互斥锁来实现的。

     

     

     集合的线程不安全问题之Map

    Map集合仍然会出现上述的java.util.ConcurrentModificationException异常。

    解决方案:

    1.使用Collections工具类的synchronizedMap()方法
    2.使用HashTable
    但HashTable也是一个古老的技术,它的所有方法也是都加了锁,并发性不好,不推荐使用。独占锁。
    3.使用ConcurrentHashMap(J.U.C提供)
    java没有为map提供写时复制的类,所以我们使用ConcurrentHashMap来解决map类线程安全的问题。ConcureentHashMap同步容器类是java5增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap与Hashtable之间。内部采用“锁分段”机制替代Hashtable的独占锁。进而提高性能。

    示例代码:

    public class UnsafeMap {
    
        public static void main(String[] args) {
            //hashMap();
           // collections();
            concurrentHashMap();
        }
    
        //多线程下HashMap线程不安全,出现并发修改异常
        public static void hashMap(){
            Map<String,Object> map= new HashMap<>();
            for (int i = 1; i <=50; i++) {
                new Thread(()->{
                    map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
        }
    
        //使用Collections工具类中的synchronizedMap构建安全map
        public static void collections(){
            Map<String,Object> map= Collections.synchronizedMap(new HashMap<>());
            for (int i = 1; i <=50; i++) {
                new Thread(()->{
                    map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
        }
    
        //使用ConcurrentHashMap
        public static void concurrentHashMap(){
            Map<String,Object> map= new ConcurrentHashMap<>();
            for (int i = 1; i <=50; i++) {
                new Thread(()->{
                    map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                    System.out.println(map);
                },String.valueOf(i)).start();
            }
        }
    }

    ConcurrentHashMap底层实现原理可以仔细阅读下面这篇博客,博主写的特详细

    https://www.jianshu.com/p/865c813f2726

  • 相关阅读:
    String Kernel SVM
    基因组印记
    用Js的eval解析JSON中的注意点
    struts2中<s:select>标签的使用
    如何在Linux中使用cron命令
    怎样解决MySQL数据库主从复制延迟的问题
    PMON failed to acquire latch, see PMON dump
    java中对List中对象排序实现
    C语言typedef关键字
    企业级内部信息统一搜索解决方案
  • 原文地址:https://www.cnblogs.com/mabaoying/p/13149939.html
Copyright © 2011-2022 走看看