zoukankan      html  css  js  c++  java
  • 图解JAVA容器核心类库

    JAVA容器详解

    类继承结构

    HashMap
      1. 对象的HashCode是用来在散列存储结构中确定对象的存储地址的。

       2. 如果两个对象的HashCode相同,即在数组中的地址相同。而数组的元素是链表。这两个对象会放在同一链表上

       3. 如何确定是同一个对象? 通过equals方法。

       4. HashMap默认的加载因子是0.75,默认最大容量是16。扩容大小:扩容原来的一倍。

       因此可以得出HashMap的默认实际容量是:0.75*16=12,到了12就会扩容。

          5. JAVA 7中的HashMap是数组和链表的结合体。

          6.JAVA 8中是数组 + 红黑树实现。size小于8时,采用数组+链表;达到8后,采用数组+红黑树。

    ConcurrentHashMap

         1.  JDK1.7版本:ReentrantLock+Segment+HashEntry

              JDK1.8版本中synchronized+CAS+HashEntry+红黑树,已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发。

         2.  查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。

          3.  定位一个元素的过程需要进行两次Hash操作。第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部

    TreeSet与TreeMap :可以保证按大小排序。

    ConcurrentSkipListSet和ConcurrentSkipListMap :和TreeSet和TreeMap一样,可以保证大小有序。

    LinkedHashSet与LinkedHashMap:加了一个双向链表,能保证添加的顺序。

    ArrayList与LinkedList:即数组和链表的优缺点。查询前者更高效,添加删除后者更高效。

    线程不安全的类

       HashMap、HashSet、ArrayList、LinkedList、TreeSet、TreeMap


    同步容器
      ArrayList  -> Vector、Stack
      HashMap   -> HashTable(key和value不能为空)
        - HashMap不是线程安全的,多线程环境容易导致CPU 100%
        - HashTable使用synchronized来保证线程安全,效率低下。
    Collections.synchronizedXXX(List,Set,Map):原理是直接使用synchronized修饰。一般并发够用,但是高并发情况下需要使用并发容器。

        Collections.synchronizedList(l1);
        Collections.synchronizedMap(new HashMap<String,String>());
        Collections.synchronizedSet(new HashSet<String>());
        Collections.synchronizedSortedMap(new TreeMap<String,String>());
        Collections.synchronizedSortedSet(new TreeSet<String>());

    并发容器 J.U.C(比同步容器更适合高并发)
       ArrayList -> CopyOnWriteArrayList  

              写写才会阻塞,写不阻塞读。 适合大小比较小且读多写少的场景

       HashSet -> CopyOnWriteArraySet    

               适合大小比较小且读多写少的场景

       TreeSet(大小顺序) -> ConcurrentSkipListSet(同步+大小顺序  

              适合大小比较小且读多写少的场景

       HashMap -> ConcurrentHashMap(同步)  

              锁分段技术-数据分成一段一段存储(一段对应一个hashEntry数组,每个数组是一个链表结构的元素) ,为每一段数据分配一把锁,多线程访问不同数据段时,就不会产生竞争了。

      TreeMap(大小顺序) -> ConcurrentSkipListMap(同步+大小顺序       

      ConcurrentLinkedQueue   高效的并发队列,是高并发中性能最好的队列,先进先出,使用链表实现。入队了出队都采用CAS算法。(线程安全的LinkedList)

    CopyOnWriteArrayList 详解

          1. 读不加锁

          2. 读写分离:写时复制的容器

              通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加, 而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。

          3. 最终一致:不能保证数据的实时一致性

              对CopyOnWriteArrayList来说,线程1读取集合里面的数据,未必是最新的数据。因为线程2、线程3、线程4四个线程都修改了CopyOnWriteArrayList里面的数据,但是线程1拿到的还是最老的那个Object[] array,新添加进去的数据并没有,所以线程1读取的内容未必准确。不过这些数据虽然对于线程1是不一致的,

               但是对于之后的线程一定是一致的,它们拿到的Object[] array一定是三个线程都操作完毕之后的Object array[],这就是最终一致。

          4.  缺点:

               不保证实时一致:在完成写入、删除和修改前,读取到的仍然是旧数据。

               非常耗内存,写入和修改操作复制一个数组。数据量大时可能造成频繁的Yong GC和Full GC

    Vector、Collections.synchronizedList 和 CopyOnWriteArrayList

    • Vector对所有操作进行了synchronized关键字修饰,性能应该比较差
    • CopyOnWriteArrayList  读不加锁,读性能较好;但是在写操作时需要进行copy操作,写性能是三者最差的。适合读操作远远多于写操作
    • Collections.synchronizedList性能较均衡,但是迭代操作并未加锁,所以需要时需要额外注意
    • Vector在迭代时进行修改也会有ConcurrentModificationException异常,可以通过加锁或者使用CopyOnWriteArrayList解决。

    跳表:ConcurrentSkipListSet和ConcurrentSkipListMap

        特点:与HashSet/HashMap相比,所有元素都是有序的。

    • 跳跃表结构是拿空间换时间的一种结构,尽管空间占用不是很大。
    • 查询、删除,平均时间复杂度都是O(logn),而插入的平均时间复杂度也是O(logn)
    • 跳跃表不同于树结构,如红黑树等,它不需要花费过多的精力进行平衡算法,这也是跳跃表的性能优越的一个方面。

                

       1. 多层结构,每一层都是一个有序的链表,

       2. 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

       3. 最底层(Level 1)的链表包含所有元素

       4. 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。

    阻塞队列

      1.ArrayBlockingQueue:数组有界阻塞队列
      2.LinkedBlockingQueue:链表有界阻塞队列,默认长度和最大长度都为Integer.MAX_VALUE
      3.PriorityBlockingQueue:支持优先级排序的无界阻塞队列,默认情况下自然顺序,支持重现compareTo()方法
      4.DelayQueue:使用PriorityQueue实现的支持延时获取元素的无界阻塞队列,元素必须实现Delayed接口,可以指定延迟多久后才能取出元素,期满才能取出。
      5.SychronousQueue:一个不存储元素的阻塞队列。每一个Put操作必须等到一个take操作,否则不能继续添加。
      6.LinkedTransferQueue:链表无界阻塞队列
      7.LinkedBlokingDeque:链表双向阻塞队列-可以从两端插入和移除元素。

        队列的底层一般分成三种:数组、链表和堆。其中,堆一般情况下是为了实现带有优先级特性的队列,暂且不考虑。

        我们就从数组和链表两种数据结构来看,

        1、基于数组线程安全的队列,比较典型的是ArrayBlockingQueue,它主要通过加锁的方式来保证线程安全;

        2、基于链表的线程安全队列分成LinkedBlockingQueue和ConcurrentLinkedQueue两大类,前者也通过锁的方式来实现线程安全,而后者以及上面表格中的LinkedTransferQueue都是通过原子变量compare and swap(以下简称“CAS”)这种不加锁的方式来实现的。

        通过不加锁的方式实现的队列都是无界的(无法保证队列的长度在确定的范围内);而加锁的方式,可以实现有界队列。在稳定性要求特别高的系统中,为了防止生产者速度过快,导致内存溢出,只能选择有界队列;同时,为了减少Java的垃圾回收对系统性能的影响,会尽量选择array/heap格式的数据结构。这样筛选下来,符合条件的队列就只有ArrayBlockingQueue。然而,ArrayBlockingQueue在实际使用过程中,会因为加锁和伪共享等出现严重的性能问题。

        队列总结:如果真的是应对高并发场景,建议使用无锁内存队列Disruptor(单机性能极致)。Log4j 2、 Storm等都有应用。

       可以参考:https://tech.meituan.com/2016/11/18/disruptor.html

  • 相关阅读:
    uva 11997
    【USACO 3.1.1】最短网络
    【USACO 2.4.5】分数化小数
    【USACO 2.4.4】回家
    【USACO 2.4.3】牛的旅行
    【USACO 2.4.2】穿越栅栏
    【USACO 2.4.1】两只塔姆沃斯牛
    【USACO 2.3.5】控制公司
    【USACO 2.3.4】货币系统
    【USACO 2.3.3】零数列
  • 原文地址:https://www.cnblogs.com/caoshouling/p/13515281.html
Copyright © 2011-2022 走看看