并发容器简述

HashTable

HashMap是线程不安全的,在多线程环境下,使用HashMap进行put操作时,可能会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。
于是有了HashTable,HashTable是线程安全的。但是HashTable线程安全的策略实在不怎么高明,将get/put等所 有相关操作都整成了synchronized的。
在这里插入图片描述

ConcurrentHashMap

由于直接加synchronized太过粗暴,基于分段式锁的ConcurrentHashMap出现

分段式

ConcurrentHashMap使用Segment(分段锁)技术,将数据分成一段一段的存储,Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,
Segment数组中每一个元素一把锁,每一个Segment元素存储的是 HashEntry数组+链表,这个和HashMap的数据存储结构一样。
当访问其中一个段数据被某个线程加锁的时候,其他段的数据也能被其他线程访问,这就使得ConcurrentHashMap不仅保证了线程安全,而且提高了性能。
但是这也引来一个负面影响:ConcurrentHashMap 定位一个元素的过程需要进行两次Hash操作,
第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的链表。所以 Hash 的过程比普通的 HashMap 要长。

jdk8后的新原理

源码分析
散列桶+cas+synchronized+红黑树(达到临界值后)

//存值
 public V put(K key, V value) {
        return putVal(key, value, false);
 }

/** Implementation for put and putIfAbsent */
 final V putVal(K key, V value, boolean onlyIfAbsent) {
     if (key == null || value == null) throw new NullPointerException();
     int hash = spread(key.hashCode()); // 计算hash
     int binCount = 0;
     for (Node<K,V>[] tab = table;;) { // 自旋,确保插入成功
         Node<K,V> f; int n, i, fh;
         if (tab == null || (n = tab.length) == 0)
             tab = initTable(); // 表为空的话,初始化表
         else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
         // 否则 插入元素 看下面的casTabAt 方法
         // cas 比较是否为null,如果null才会设置break,否则到else
             if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                 break;                   // no lock when adding to empty bin
         }
         else if ((fh = f.hash) == MOVED)
             tab = helpTransfer(tab, f);
         else {
             V oldVal = null;
             // 其他情况下,加锁保持 synchronized 
             // 加锁 ,锁的是当前插槽上的头节点f(类似分段锁)
             synchronized (f) {
                 if (tabAt(tab, i) == f) {
                     if (fh >= 0) {
                         binCount = 1;//当前插槽上的节点数量
                         //沿着Node链往后找
                         for (Node<K,V> e = f;; ++binCount) {
                             K ek;
                             //如果找到相同key,说明之前put过覆盖
                             if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {
                                 oldVal = e.val;
                                 //put方法上 onlyIfAbsent=flase 要不要覆盖
                                 if (!onlyIfAbsent)
                                     e.val = value;
                                 break;
                             }
                             //否则,新key, 新node插入到最后
                             Node<K,V> pred = e;
                             if ((e = e.next) == null) {
                                 pred.next = new Node<K,V>(hash, key, value, null);
                                 break;
                             }
                         }
                     }
                     // 如果是红黑树,说明已经转化过,按树的规则放入Node
                     else if (f instanceof TreeBin) {
                         Node<K,V> p;
                         binCount = 2;
                         if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {
                             oldVal = p.val;
                             if (!onlyIfAbsent)
                                 p.val = value;
                         }
                     }
                 }
             }
             if (binCount != 0) {
                 //如果节点数到达临界值,链表转成树 
                 if (binCount >= TREEIFY_THRESHOLD)
                     treeifyBin(tab, i);
                 if (oldVal != null)
                     return oldVal;
                 break;
             }
         }
     }
     //计数
     addCount(1L, binCount);
     return null;
 }
//compareAndSwapObject 典型的CAS
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 取值
public V get(Object key) {
   Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
     int h = spread(key.hashCode());
     //判断table是不是空的,当前桶上是不是空的 如果是空 返回null
     if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
         //找到对应hash槽的第一个node,如果key相等,返回value
         if ((eh = e.hash) == h) {
             if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                 return e.val;
         }
         //如果正在扩容,不影响,继续顺着node找即可
         else if (eh < 0)
             return (p = e.find(h, key)) != null ? p.val : null;
         //其他情况,遍历 对比key 找到后返回value    
         while ((e = e.next) != null) {
             if (e.hash == h &&
                 ((ek = e.key) == key || (ek != null && key.equals(ek))))
                 return e.val;
         }
     }
     return null;
 }

总结:
put过程:
1.根据key的hash值定位到桶位置
2.如果table为空if,先初始化table。
3.如果table当前桶里没有node,cas添加元素。成功则跳出循环,失败则进入下一轮for循环。
4.判断是否有其他线程在扩容,有则帮忙扩容,扩容完成再添加元素。
5.如果桶的位置不为空,遍历该桶的链表或者红黑树,若key已存在,则覆盖,不存在则将key插入到链表或红黑树的尾部。

get过程:
1.根据key的hash值定位到桶位置。
2.map是否初始化,没有初始化则返回null
3.定位的桶是否有头结点,没有返回null
4.是否有其他线程在扩容,有的话调用find方法沿node指针往后查找。扩容与find可以并行,因为node的next指针不会变
5.若没有其他线程在扩容,则遍历桶对应的链表或者红黑树,使用equals方法进行比较。key相同则返回value,不存 在则返回null

并发容器

  • ConcurrentHashMap
    对应:HashMap package
    目标:代替Hashtable、synchronizedMap,使用最多
    原理:前面详细介绍过

  • CopyOnWriteArrayList
    对应:ArrayList
    目标:代替Vector、synchronizedList
    原理:高并发往往是读多写少的特性,读操作不加锁,而对写操作加Lock独享锁,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性。
    查看源码:volatile array,lock加锁,数组复制

  • CopyOnWriteArraySet
    对应:HashSet
    目标:代替synchronizedSet
    原理:与CopyOnWriteArrayList实现原理类似。

  • ConcurrentSkipListMap
    对应:TreeMap
    目标:代替synchronizedSortedMap(TreeMap)
    原理:基于Skip list(跳表)来代替平衡树,按照分层key上下链接指针来实现。

  • ConcurrentSkipListSet
    对应:TreeSet
    目标:代替synchronizedSortedSet(TreeSet)
    原理:内部基于ConcurrentSkipListMap实现,原理一致

  • ConcurrentLinkedQueue
    对应:LinkedList 、无界线程安全队列
    原理:通过队首队尾指针,以及Node类元素的next实现FIFO队列

  • BlockingQueue
    对应:Queue
    特点:拓展了Queue,增加了可阻塞的插入和获取等操作
    原理:通过ReentrantLock实现线程安全,通过Condition实现阻塞和唤醒
    实现类:
    LinkedBlockingQueue:基于链表实现的可阻塞的FIFO队列
    ArrayBlockingQueue:基于数组实现的可阻塞的FIFO队列
    PriorityBlockingQueue:按优先级排序的队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值