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

  • 相关阅读:
    Python动态展示遗传算法求解TSP旅行商问题
    MOEAD算法中均匀权向量的实现---Python
    HDU 5294 多校第一场1007题 最短路+最小割
    POJ 3261 Milk Patterns sa+二分
    HDU 4292 FOOD 2012 ACM/ICPC Asia Regional Chengdu Online
    CodeForces 201A Clear Symmetry
    POJ 1679 The Unique MST 确定MST是否唯一
    POJ 3268 Silver Cow Party 最短路 基础题
    POJ 2139 SIx Degrees of Cowvin Bacon 最短路 水題
    POJ2229 Sumsets 基礎DP
  • 原文地址:https://www.cnblogs.com/mabaoying/p/13149939.html
Copyright © 2011-2022 走看看