集合框架的对比以及集合的线程安全化

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

posted @ 2020-09-11 12:08  xiao_lin  阅读(231)  评论(0编辑  收藏  举报