并发容器

位运算符实际开发用途场景

        可以用于权限和商品的情景

hashMap在1.7中会造成死循环原因

        hashMap在jdk1.7的版本使用时,如果是两个线程向map里put数据时会产生环形的数据结构,造成死循环,导致cpu利用率变为100%。产生环形数据结构原因:当两个线程并发的进行扩容时,一个线程完成了全部的扩容操作,而另一个线程执行过程中被挂起了,而这个挂起的线程被恢复执行以后,在自己的newtable里进行一次扩容操作,在进行头插法的链表操作,会造成两个元素之间next指针相互指着,形成一种循环链表而导致一个死循环(这只是一个简要说明,如果想彻底明白的话需要根据老师的笔记自己在纸上画图理解)。

hashMap的一次扩容过程

        1、取当前table的2倍作为新table的大小;

        2、根据算出的新table的大小new出一个新的Entry数组来,名为newTable;

        3、轮询原table的每一个位置,将每一个位置上连接的Entry,算出在新的table上的位置,并以链表形式连接

        4、原table上的所有Entry全部轮询完毕之后,意味着原table上面的所有Entry已经移到新的table上,hashmap中的table指向newTable。

hashMap插入数据方式

        hashMap向链表中插入元素时使用头插法插入元素的。

concurrentHashMap

      jdk1.7中

         数据结构:首先有一个segment的数组(初始化后不可扩容),每一个segment下有一个table数组,是一个链表,数据结构为HashEntry。segment具有可重入锁,由于有锁的机制,可以保护每一个segment下挂载的HashEntry。初始化缺省的并发度的值为16(也就是segment的数组长度)。初始化时只会创建segment[0]的tabel,其余的会在put时初始化。同时值都需要符合2的n次方。

         get方法为什么不需要加锁:因为hashEntry定义结构时,使用了volatile以及final关键字,保证拿到的值都是最新的。

        put方法:先计算key的hash找到segment的对应的数组,然后使用CAS初始化segment,然后进入到HashEntry对象里,循环当前链表,如果没找到这个key,使用头插法把当前插入到链表中,如果找到了,那就替换掉原来的key的对象。

        高并发时不建议的操作:高并发时不推荐使用size()和containsValue()方法,因为会把所有的segment加锁,比较重。

     jdk1.8中

        数据结构:采用数组加链表加红黑树。取消了1.7中的segment,而1.7中segment下的hashEntry被Node取代了。当链表过长时(超过8)就会从链表转为红黑树,当红黑树节点的个数小于6的时候会从红黑树变为链表。Node中的数据结构也是用final和volatile修饰的。1.8中的conCurrentHashMap中的TreeNode继承的是Node,而hashMap继承的是LinkedHashMap.Entry。

        初始化以及插入数据方式:初始化时,只是给sizeCtl附了一个值,其他的什么都没做。向Node中插入元素用的是尾插法。

        put流程:首先根据key计算hash值,然后检查table上是否有元素,没有元素则直接放到这个table数组上面,如有的话就拿到节点的树,根据节点下挂载的是红黑树还是链表,按照两种数据结构进行插入,插入完成后,判定是否要将链表转成红黑树,然后判定是否要在进行一次扩容的操作。

        remove流程:和put类似,只是插入换成移除,如果是红黑树则转成链表。

ConCurrentSkipListMap与ConCurrentSkipListSet

        两个存的都是有序的map和set.ConCurrentSkipListSet内部实现其实就是对ConCurrentSkipListMap的一个包装而已,同理,TreeSet也是对TreeMap的一个包装。两个是线程安全的。

ConcurrentLinkedQueue

        ConcurrentLinkedQueue是LinkedList的并发版本。Add()是插入元素,offer()是获取元素,peek是检索元素但是并不移除元素,poll是移除元素。

CopyOnWriteArrayList和CopyOnWriteArraySet

        CopyOnWriteArrayList和CopyOnWriteArraySet是写时复制容器。才用了读写分离的思想。用于场景:大部分是读,偶尔有写的现象。比如白名单黑名单。缺点是性能很低,内存开销大,数据一致性差。

阻塞队列

    所有的阻塞队列:
    1、ArrayBlockingQueue:一个由数组组成的有界阻塞队列

    2、LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列

    3、PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

    4、DelayQueue:一个使用优先级队列实现的无界阻塞队列。

    5、SynchronousQueue:一个不存储元素的阻塞队列。

    6、LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

    7、LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

    原理:采用等待通知模式。

    ArrayBlockingQueue与LinkedBlockingQueue:

    ArrayBlockingQueue和LinkedBlockingQueue除了数据结构不同,在实现上ArrayBlockingQueue用的是一把锁,而LinkedBlockingQueue用的是两把锁,生产者一把锁,消费者一把锁。

    


相关知识点问答记录

问:hashMap和hashTable有什么区别?

答:hashMap线程不安全,hashTable是线程安全的但是性能没有hashMap高,hashMap允许key和value为null,而hashTable不允许。hashMap初始化为16,hashTable初始化为11.扩容时hashMap为2倍,而hashTable为2倍+1。hashMap在hash值散列的时候会重新计算hash,而hashTable会直接使用hashCode值。

问:Java中的另一个线程安全的与HashMap极其类似的类是什么,同样是线程安全,它与HashTable在线程实现上有什么不同?

答:是conCurrentHashMap。hashTable实现上使用了synchronize关键字,锁的是整个的map,高并发下竞争越激烈,效率越低,而conCurrentHashMaphashMap在1.7中采用分段锁的方式,1.8中采用CAS加Synchronize加分段锁,大大提升了效率。

问:HashMap和conCurrentHashMap区别?

答:一个是线程不安全,一个是线程安全。HashMap允许键值对为null,而conCurrentHashMap不允许。

问:为什么conCurrentHashMap比hashTable效率要高?

答:因为hashTable用了一把锁,锁住了整个的map,多个线程竞争一把锁容易阻塞。而conCurrentHashMap采用了分段锁,这样锁的力度就降低了。

问:conCurrentHashMap锁机制具体分析(jdk1.7 vs jdk1.8)?

答:1.7里采用了分段锁机制实现了并发的更新操作,而底层采用了数组加链表的结构,数组是segment,链表是hashEntry,segment继承可重入锁来充当了锁的角色,由每个segment保护自己下面所属的数据,hashEntry组成了链表被每个segmeng保护。而1.8采用了node+cas+syn关键字来保证并发安全,取消了segment这一层,同时加入了红黑树机制,红黑树加链表的相互转换能提升性能。

问:conCurrentHashMap在jdk1.8中,为什么要使用内置锁synchronize来代替重入锁ReenTrantLock?

答:第一,开发团队在1.8中对syn关键字做了大量性能优化,而且基于虚拟机本身的优化空间更大,更加自然。第二,显示锁是一个类,意味着显示锁是一个对象需要消耗内存的,而syn的内存的开销更小。

问:1.8下conCurrentHashMap简单介绍?

答:在conCurrentHashMap里有一个比较重要的成员变量,sizeCtl。它控制了初始化的大小以及扩容的大小以及当前conCurrentHashMap是否进行初始化以及是否正在进行扩容。第二,重要的数据结构,Node。继承至Entry,用于存储数据,是存储的基本单元。同时基于Node的基础上,支持红黑树又扩展了TreeNode和TreeBin。TreeNode用于在红黑树上存储数据,而TreeBin封装了TreeNode,还提供了属性值。第三,get时计算hash值,如果定位到了table则直接返回,如果不是则根据当前节点的类型,分别按照链表和红黑树的方式查找当前元素所在的位置。如果进行put时,如果没有进行初始化,那么进行初始化,如果没有hash冲突,则使用cas操作进行插入数据,否则对当前table的元素进行加锁,如果是链表插入则按照尾插法进行插入,如果是红黑树则按照红黑树的方式插入,插入后如果链表长度大于8则转成红黑树。插入后如果需要扩容则再扩容。扩容方法:为了提高效率,工作线程会帮助进行一起进行并发的扩容,效率会很高,为了避免扩容时多个线程对节点的一个冲突,每个线程会用一个步长的形式进行节点的扩容操作。

问:conCurrentHashMap的并发度是什么?

答:1.7为16,1.8 中并发度则无太大的实际意义了,主要用处就是当设置的初始容量小于并发度,将初始容量提升至并发度大小。

CopyOnWriteArrayList和CopyOnWriteArraySet是写时复制容器。才用了读写分离的思想。用于场景:大部分是读,偶尔有写的现象。比如白名单黑名单。缺点是性能很低,内存开销大,数据一致性差

采用等待通知模式

你可能感兴趣的:(并发容器)