zoukankan      html  css  js  c++  java
  • 集合框架的对比以及集合的线程安全化

    1.集合和集合框架

    其实是我自己有些整不明白的,通常集合指的是Collaction接口下的接口或者实现类

    集合框架目前我的印象中指的是Collaction接口下和Map接口下的接口和实现类

    2.Collection:

      1):List接口下:

        ①:ArrayList:动态数组

        ②:LinkedList:链表

        ③:Vector:动态数组

        ④:相同点和区别:

          ArrayList和Vector:同:动态数组

                     继承自AbstractList抽象类

                     实现了List(List集合接口),RandomAccess(实现随机访问),Cloneable(克隆接口), Serializable(序列化)接口

                   异:Vector的方法都是是线程安全的,

                     ArrayList不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。

                     LinkedList的方法跟ArrayList一样,也不是线程安全的

          ArrayList和LinkedList:同:实现了List,Cloneable,Serializable接口

                       都不是线程安全的

                      异:ArrayList是动态数组,继承自AbstractList抽象类,实现了RandomAccess接口,可以进行随机访问,查找操作循序,增删操作慢

                       LinkedList是链表,继承自AbstrackSequentialList抽象类,实现了Deque接口,只能顺序访问,查找操作慢,增删操作快

                       ps:注意增删过程和增删操作:增删整个过程中,由于需要先查找再增删,所以在整个过程中时间复杂度是一样的

                        (关于时间复杂度后续有时间会写一点,

                        现在简单来讲就是:数组查询时间复杂度为O(1),复制(增删过程)时间复杂度为O(n);

                        链表查询时间复杂度为O(n),增删过程时间复杂度为O(1))

      2)Set接口下:

        HashSet:底层是HashMap,继承自AbstractSet抽象类,实现了Set,Cloneable,serializable接口

        EntrySet:常见于HashMap,开发者无法new,继承自AbstractSet抽象类,常用作便利HashMap的键值对

      3)List接口下的集合和Set接口下的集合的区别:List下的集合是有序可重复集合,Set下的集合是无序不可重复集合

    3.Map:链表和数组的结合体

      1)HashMap:数组方式存储key,value构成的Entry对象(来自:https://blog.csdn.net/gldemo/article/details/44653787),

              继承自AbstractMap抽象类,实现了Map,Cloneable,Serializable接口

      2)Hashtable:继承自Dictionary抽象类,实现了Map,Cloneable,Serializable接口

      3)相同点和区别:(继承的抽象类和实现接口不再赘述)

         HashMap:允许有一个key值为null和任意多个value值为null,是非线程安全的,遍历使用的是Iterator

         Hashtable:的key和value都不允许有null,而且是线程安全的,遍历使用的是Enumeration。

         HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。

          所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,

          但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。

          但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

            (关于迭代器未曾仔细学习,来自:https://blog.csdn.net/sinat_35821285/article/details/80225116)

      4)和HashSet的比较:(继承的抽象类和实现接口不再赘述)

          HashMap:存储键值对

          HashSet:存储对象(可以认为是值)

      5)TreeSet和TreeMap不常用,所以这两者的联系和区别不做描述,兴趣来了可以

          参考:https://blog.csdn.net/sinat_35821285/article/details/80225116

             https://blog.csdn.net/gldemo/article/details/44653787

    4.线程安全

      1)工作原理:    

        ①:jvm有一个main memory,而每个线程有自己的working memory(线程的工作区)

        ②:一个线程对一个variable(线程共享数据)进行操作时,都要在自己的working memory里面建立一个copy,操作完之后再写入main memory。

        ③:多个线程同时操作同一个variable,就可能会出现不可预知的结果。根据上面的解释,很容易想出相应的scenario。

        ④:而用synchronized的关键是建立一个monitor,这个monitor可以是要修改的variable也可以其他你认为合适的object

        ⑤:通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完load到working memory -> use&assign -> store到main memory的过程,

          才会释放它得到的锁。这样就实现了所谓的线程安全。

      2)简图:

       ps:以下内容来自:https://www.cnblogs.com/lzhdonald/archive/2020/07/17/13332727.html

       3)集合框架的线程安全:

    线程不安全集合 线程安全集合
    ArrayList Vector
    LinkedList  
    HashMap Hashtable
    HashSet  
    TreeMap  
    TreeSet  

       4)解决方案:

        ① ArrayList:

        •     
        • 使用Vector(效率很低);
        • 使用Collections集合工具类的static方法synchronizedList,将ArrayList的集合传入Collections.synchronizedList(List list)方法,使其成为线程安全的集合(原理未学习):内部直接将接受的List对象传递给静态内部类SynchronizedList对象,然后Collections.synchronizedList(new ArrayList<>())返回的List对象的调用方法都是直接调用输入List对象的方法,但是加了synchronized,类似装饰器模式,也是对输入List的一种增强,源码如下:
           1 static <T> List<T> synchronizedList(List<T> list, Object mutex) {
           2     return (list instanceof RandomAccess ?
           3             new SynchronizedRandomAccessList<>(list, mutex) :
           4             new SynchronizedList<>(list, mutex));
           5 }
           6  
           7 static class SynchronizedList<E>
           8     extends SynchronizedCollection<E>
           9     implements List<E> {
          10     private static final long serialVersionUID = -7754090372962971524L;
          11  
          12     final List<E> list;
          13  
          14     SynchronizedList(List<E> list) {
          15         super(list);
          16         this.list = list;
          17     }
          18     SynchronizedList(List<E> list, Object mutex) {
          19         super(list, mutex);
          20         this.list = list;
          21     }
          22  
          23     public boolean equals(Object o) {
          24         if (this == o)
          25             return true;
          26         synchronized (mutex) {return list.equals(o);}
          27     }
          28     public int hashCode() {
          29         synchronized (mutex) {return list.hashCode();}
          30     }
          31  
          32     public E get(int index) {
          33         synchronized (mutex) {return list.get(index);}
          34     }
          35     public E set(int index, E element) {
          36         synchronized (mutex) {return list.set(index, element);}
          37     }
          38     public void add(int index, E element) {
          39         synchronized (mutex) {list.add(index, element);}
          40     }
          41     public E remove(int index) {
          42         synchronized (mutex) {return list.remove(index);}
          43     }
          44  
          45     public int indexOf(Object o) {
          46         synchronized (mutex) {return list.indexOf(o);}
          47     }
          48     public int lastIndexOf(Object o) {
          49         synchronized (mutex) {return list.lastIndexOf(o);}
          50     }
          51  
          52     public boolean addAll(int index, Collection<? extends E> c) {
          53         synchronized (mutex) {return list.addAll(index, c);}
          54     }
          55  
          56     public ListIterator<E> listIterator() {
          57         return list.listIterator(); // Must be manually synched by user
          58     }
          59  
          60     public ListIterator<E> listIterator(int index) {
          61         return list.listIterator(index); // Must be manually synched by user
          62     }
          63  
          64     public List<E> subList(int fromIndex, int toIndex) {
          65         synchronized (mutex) {
          66             return new SynchronizedList<>(list.subList(fromIndex, toIndex),
          67                                         mutex);
          68         }
          69     }
          70  
          71     @Override
          72     public void replaceAll(UnaryOperator<E> operator) {
          73         synchronized (mutex) {list.replaceAll(operator);}
          74     }
          75     @Override
          76     public void sort(Comparator<? super E> c) {
          77         synchronized (mutex) {list.sort(c);}
          78     }
          79  
          80     private Object readResolve() {
          81         return (list instanceof RandomAccess
          82                 ? new SynchronizedRandomAccessList<>(list)
          83                 : this);
          84     }
          85 }
          View Code 
        • 使用CopyOnWriteArrayList,读写分离的思想读写分离的思想,在并发读的时候不需要加锁,因为它能够保证并发读的情况下不会添加任何元素。而在并发写的情况下,需要先加锁,但是并不直接对当前容器进行写操作。而是先将当前容器进行复制获取一个新的容器,进行完并发写操作之后,当之前指向原容器的引用更改指向当前新容器。也就是说,并发读和并发写是针对不同集合,因此不会产生并发异常,源码如下:
           1 // CopyOnWriteArrayList.java
           2 public boolean add(E e) {
           3     // 写操作加锁
           4     final ReentrantLock lock = this.lock;
           5     lock.lock();
           6     try {
           7         // 原有容器复制一份
           8         Object[] elements = getArray();
           9         int len = elements.length;
          10         // 创建一个容器,将原来的数据复制到新容器中,并且还有一个位置空余
          11         Object[] newElements = Arrays.copyOf(elements, len + 1);
          12         // 将新元素添加到空余位置
          13         newElements[len] = e;
          14         // 将原来指向旧容器的引用指向新容器
          15         setArray(newElements);
          16         return true;
          17     } finally {
          18         // 写操作完成,解锁
          19         lock.unlock();
          20     }
          21 }
          22  
          23 public E set(int index, E element) {
          24     // 更新操作类似
          25     final ReentrantLock lock = this.lock;
          26     lock.lock();
          27     try {
          28         Object[] elements = getArray();
          29         E oldValue = get(elements, index);
          30  
          31         if (oldValue != element) {
          32             int len = elements.length;
          33             Object[] newElements = Arrays.copyOf(elements, len);
          34             newElements[index] = element;
          35             setArray(newElements);
          36         } else {
          37             // Not quite a no-op; ensures volatile write semantics
          38             setArray(elements);
          39         }
          40         return oldValue;
          41     } finally {
          42         lock.unlock();
          43     }
          44 }
          45  
          46 // 读操作不加锁
          47 private E get(Object[] a, int index) {
          48     return (E) a[index];
          49 }
          View Code

        ②HashSet:

        •     
        • 使用Collections集合工具类的static方法SynchronizedSet,将HashSet的集合传入Collections.synchronizedSet(Set set)方法,使其成为线程安全的集合;
        • CopyOnWriteArraySet:也是写时复制思想,但是内部还是使用CopyOnWriteArrayList实现,源码如下:
           1 public class CopyOnWriteArraySet<E> extends AbstractSet<E>
           2         implements java.io.Serializable {
           3     private static final long serialVersionUID = 5457747651344034263L;
           4  
           5     private final CopyOnWriteArrayList<E> al;
           6  
           7     /**
           8      * Creates an empty set.
           9      */
          10     public CopyOnWriteArraySet() {
          11         // 构造器内部实例化了一个CopyOnWriteArrayList
          12         al = new CopyOnWriteArrayList<E>();
          13     }
          14     // ...
          15 }
          View Code

        ③HashMap:      

        •     
        • 使用Collections集合工具类的静态方法synchronizedMap,将HashMap的集合传入Collections.synchronizedMap(Map map)方法,使其成为线程安全的Map
        • 使用juc包下ConcurrentHashMap类

    TreeSet和TreeMap异同参考:

    https://blog.csdn.net/gldemo/article/details/44653787

    https://blog.csdn.net/sinat_35821285/article/details/80225116

    线程安全测试代码参考:

    https://www.cnblogs.com/lzhdonald/archive/2020/07/17/13332727.html

  • 相关阅读:
    交叉编译环境软件搭建
    (C)struct结构体
    (C)字节对齐#pragma pack()
    常用bluetooth协议
    (C/C++)register关键字
    Android学习
    (C)*p++和*++p区别
    java文件末尾追加内容的两种方式
    java1.7集合源码阅读: Stack
    java1.7集合源码阅读: Vector
  • 原文地址:https://www.cnblogs.com/xiao-lin-unit/p/13650950.html
Copyright © 2011-2022 走看看