concurentHashMap

3 篇文章 0 订阅

concurentHashMap

concurentHashMap是什么

coneurentHashMap 是Map的一个实现类,它是在jdk1.5中加入的,在jdk1.6/1.7中的主要实现原理是segment分段锁,不再使用和hashTable一样的synchronized一样的关键字对整个方法进行加锁,使用segment分段锁能够保证Map的多线程安全。concurentHashMap中,就是把Map分成了N个Segment ,put和get的时候,都是根据key.hashCode()算出放到那个Segment中。
put方法时
get方法时
在这里插入图片描述

concurentHashMap与hashTable、hashMap的区别:

1、hashMap线程不安全。
多线程环境下,使用hashMap进行put操作可能会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用hashMap。
2、hahsTable线程安全但效率低下
hashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下 hashTable的效率非常低下。当一个线程访问hashTable的同步方法时,其他线程再访问这个同步方法时,可能会进入到阻塞或轮询状态。假如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,也不能使用get方法获取元素。竞争越激烈效率越低。
3、concurentHashMap线程安全,高效
将数据分成一段段的进行存储,然后给每一段数据配一把锁,当一个线程占用一个锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。
concurentHashMap是Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在concurentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

concurentHashMap特性:

concurentHashMap中默认是把Segments初始化长度为16的数组。

concurentHashMap锁的方式是稍微细粒度的。它将hash表分为16个桶(默认),诸如get、put、remove等常用操作只锁当前需要用到的桶。这样的话原来只能一个线程进入,现在能同时16个写线程进入(写线程才需要锁定,读线程几乎不受限制),大大提升了并发能力。

concurentHashMap的读取并发是一大亮点,在度取的大多时候都没有用到锁定,所以读取操作几乎是完全的并发操作,而写操作锁定的粒度又非常细,就变得更加的快速,只有在求size等操作时才需要锁定整个表。

在迭代时,concurentHashMap使用了不同于传统集合的快速失败迭代器,而是另一种迭代方式,称为弱一致迭代器。
在这种迭代方式中,当iterator被创建后集合再发生改变就不再抛出ConcurentModificationException,取而代之的是在改变时new新的数据而不影响原有数据,iterator完成后再将头指针替换为新的数据,这样iterator线程可使用原来老的数据,而写线程也可以并发的完成改变,保证了多个线程并发执行的连续性和扩展性,是性能提升的关键。

ConcurentHashMap的结构

jdk1.7实现

采用Segment+HashEntry的方式进行实现,结构如下:
在这里插入图片描述
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁的角色;HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁,如下图所示。
在这里插入图片描述
ConcurrentHashMap初始化时,计算出Segment数组的大小size和每个Segment中HashEntry数组的大小cap,并初始化Segment数组的第一个元素;其中size大小为2的幂次方,默认为16,cap大小也是2的幂次方,最小值为2,最终结果根据根据初始化容量initialCapacity进行计算,其中Segment在实现上继承了ReentrantLock,这样就自带了锁的功能。

当执行put方法插入数据时,根据key的hash值,在Segment数组中找到相应的位置,如果相应位置的Segment还未初始化,则通过CAS进行赋值,接着执行Segment对象的put方法通过加锁机制插入数据。

1、线程A执行tryLock()方法成功获取锁,则把HashEntry对象插入到相应的位置;
2、线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B;
3、当线程A执行完插入操作时,会通过unlock()方法释放锁,接着唤醒线程B继续执行;

jdk1.8实现

concurentHashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树(提高查找效率)。

基本属性
  // node数组最大容量:2^30=1073741824  

  private  static  final  int  MAXIMUM_CAPACITY =  1  <<  30    ;  

  // 默认初始值,必须是2的幂数  

  private  static  final  int  DEFAULT_CAPACITY =  16    ;  

  //数组可能最大值,需要与toArray()相关方法关联  

  static  final  int  MAX_ARRAY_SIZE = Integer.MAX_VALUE -  8    ;  

  //并发级别,遗留下来的,为兼容以前的版本  

  private  static  final  int  DEFAULT_CONCURRENCY_LEVEL =  16    ;  

  // 负载因子  

  private  static  final  float  LOAD_FACTOR =  0    .75f;  

  // 链表转红黑树阀值,> 8 链表转换为红黑树  

  static  final  int  TREEIFY_THRESHOLD =  8    ;  

  //树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))  

  static  final  int  UNTREEIFY_THRESHOLD =  6    ;  

  static  final  int  MIN_TREEIFY_CAPACITY =  64    ;  

  private  static  final  int  MIN_TRANSFER_STRIDE =  16    ;  

  private  static  int  RESIZE_STAMP_BITS =  16    ;  

  // 2^15-1,help resize的最大线程数  

  private  static  final  int  MAX_RESIZERS = (    1  << (    32  - RESIZE_STAMP_BITS)) -  1    ;  

  // 32-16=16,sizeCtl中记录size大小的偏移量  

  private  static  final  int  RESIZE_STAMP_SHIFT =  32  - RESIZE_STAMP_BITS;  

  // forwarding nodes的hash值  

  static  final  int  MOVED     = -    1    ;  

  // 树根节点的hash值  

  static  final  int  TREEBIN   = -    2    ;  

  // ReservationNode的hash值  

  static  final  int  RESERVED  = -    3    ;  

  // 可用处理器数量  

  static  final  int  NCPU = Runtime.getRuntime().availableProcessors();  

  //存放node的数组  

  transient  volatile  Node<K,V>[] table;  

  /*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义  

  *当为负数时:-    1    代表正在初始化,-N代表有N-    1    个线程正在 进行扩容  

  *当为    0    时:代表当时的table还没有被初始化  

  *当为正数时:表示初始化或者下一次进行扩容的大小  
*/

  private  transient  volatile  int  sizeCtl;  

基本属性定义了concurentHashMap的一些边界以及操作时的一些控制。

核心
1、Node

concurentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据。
Node数据结构很简单,就是一个链表,但是只允许对数据进行查找,不允许进行修改。

  static  class  Node<K,V>  implements  Map.Entry<K,V> {  

  //链表的数据结构  

  final  int  hash;  

  final  K key;  

  //val和next都会在扩容时发生变化,所以加上volatile来保持可见性和禁止重排序  

  volatile  V val;  

  volatile  Node<K,V> next;  

  Node(    int  hash, K key, V val, Node<K,V> next) {  

  this    .hash = hash;  

  this    .key = key;  

  this    .val = val;  

  this    .next = next;  

  }  

  public  final  K getKey()       {  return  key; }  

  public  final  V getValue()     {  return  val; }  

  public  final  int  hashCode()   {  return  key.hashCode() ^ val.hashCode(); }  

  public  final  String toString(){  return  key +  "="  + val; }  

  //不允许更新value   

  public  final  V setValue(V value) {  

  throw  new  UnsupportedOperationException();  

  }  

  public  final  boolean  equals(Object o) {  

  Object k, v, u; Map.Entry<?,?> e;  

  return  ((o  instanceof  Map.Entry) &&  

  (k = (e = (Map.Entry<?,?>)o).getKey()) !=  null  &&  

  (v = e.getValue()) !=  null  &&  

  (k == key || k.equals(key)) &&  

  (v == (u = val) || v.equals(u)));  

  }  

  //用于map中的get()方法,子类重写  

  Node<K,V> find(    int  h, Object k) {  

  Node<K,V> e =  this    ;  

  if  (k !=  null    ) {  

  do  {  

  K ek;  

  if  (e.hash == h &&  

  ((ek = e.key) == k || (ek !=  null  && k.equals(ek))))  

  return  e;  

  }  while  ((e = e.next) !=  null    );  

  }  

  return  null    ;  

  }  

  }  
2、TreeNode

继承Node,数据结构换成了二叉树结构,它是红黑树的数据存储结构,用于红黑树中存储数据,当链表的节点数大于8时会转换成红黑树的结构,就是通过TreeNode作为存储结构代替Node来转换成红黑树的。

  static  final  class  TreeNode<K,V>  extends  Node<K,V> {  

  //树形结构的属性定义  

  TreeNode<K,V> parent;  // red-black tree links  

  TreeNode<K,V> left;  

  TreeNode<K,V> right;  

  TreeNode<K,V> prev;  // needed to unlink next upon deletion  

  boolean  red;  //标志红黑树的红节点  

  TreeNode(    int  hash, K key, V val, Node<K,V> next,  

  TreeNode<K,V> parent) {  

  super    (hash, key, val, next);  

  this    .parent = parent;  

  }  

  Node<K,V> find(    int  h, Object k) {  

  return  findTreeNode(h, k,  null    );  

  }  

  //根据key查找 从根节点开始找出相应的TreeNode,  

  final  TreeNode<K,V> findTreeNode(    int  h, Object k, Class<?> kc) {  

  if  (k !=  null    ) {  

  TreeNode<K,V> p =  this    ;  

  do      {  

  int  ph, dir; K pk; TreeNode<K,V> q;  

  TreeNode<K,V> pl = p.left, pr = p.right;  

  if  ((ph = p.hash) > h)  

  p = pl;  

  else  if  (ph < h)  

  p = pr;  

  else  if  ((pk = p.key) == k || (pk !=  null  && k.equals(pk)))  

  return  p;  

  else  if  (pl ==  null    )  

  p = pr;  

  else  if  (pr ==  null    )  

  p = pl;  

  else  if  ((kc !=  null  ||  

  (kc = comparableClassFor(k)) !=  null    ) &&  

  (dir = compareComparables(kc, k, pk)) !=  0    )  

  p = (dir <  0    ) ? pl : pr;  

  else  if  ((q = pr.findTreeNode(h, k, kc)) !=  null    )  

  return  q;  

  else  

  p = pl;  

  }  while  (p !=  null    );  

  }  

  return  null    ;  

  }  

  }  
3、TreeBin

封装TreeNode的容器,提供转换红黑树的一些条件和锁的控制。

  static  final  class  TreeBin<K,V>  extends  Node<K,V> {  

	  //指向TreeNode列表和根节点  
	
	  TreeNode<K,V> root;  
	
	  volatile  TreeNode<K,V> first;  
	
	  volatile  Thread waiter;  
	
	  volatile  int  lockState;  
	
	  // 读写锁状态  
	
	  static  final  int  WRITER =  1    ;  // 获取写锁的状态  
	
	  static  final  int  WAITER =  2    ;  // 等待写锁的状态  
	
	  static  final  int  READER =  4    ;  // 增加数据时读锁的状态  
	
	  /**  
	
	  * 初始化红黑树  
	
	  */  
	
	  TreeBin(TreeNode<K,V> b) {  
	
	  super    (TREEBIN,  null    ,  null    ,  null    );  
	
	  this    .first = b;  
	
	  TreeNode<K,V> r =  null    ;  
	
	  for  (TreeNode<K,V> x = b, next; x !=  null    ; x = next) {  
	
	  next = (TreeNode<K,V>)x.next;  
	
	  x.left = x.right =  null    ;  
	
	  if  (r ==  null    ) {  
	
	  x.parent =  null    ;  
	
	  x.red =  false    ;  
	
	  r = x;  
	
	  }  
	
	  else  {  
	
	  K k = x.key;  
	
	  int  h = x.hash;  
	
	  Class<?> kc =  null    ;  
	
	  for  (TreeNode<K,V> p = r;;) {  
	
	  int  dir, ph;  
	
	  K pk = p.key;  
	
	  if  ((ph = p.hash) > h)  
	
	  dir = -    1    ;  
	
	  else  if  (ph < h)  
	
	  dir =  1    ;  
	
	  else  if  ((kc ==  null  &&  
	
	  (kc = comparableClassFor(k)) ==  null    ) ||  
	
	  (dir = compareComparables(kc, k, pk)) ==  0    )  
	
	  dir = tieBreakOrder(k, pk);  
	
	  TreeNode<K,V> xp = p;  
	
	  if  ((p = (dir <=  0    ) ? p.left : p.right) ==  null    ) {  
	
	  x.parent = xp;  
	
	  if  (dir <=  0    )  
	
	  xp.left = x;  
	
	  else  
	
	  xp.right = x;  
	
	  r = balanceInsertion(r, x);  
	
	  break    ;  
	
	  }  
	
	  }  
	
	  }  

  }  

  this    .root = r;  

  assert  checkInvariants(root);  

  }  
  ......  
  }  
构造
 //构造方法
 public ConcurrentHashMap(int initialCapacity) {
     if (initialCapacity < 0)//判断参数是否合法
         throw new IllegalArgumentException();
     int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                MAXIMUM_CAPACITY ://最大为2^30
                tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));//根据参数调整table的大小
     this.sizeCtl = cap;//获取容量
     //ConcurrentHashMap在构造函数中只会初始化sizeCtl值,并不会直接初始化table
 }
 //调整table的大小
 private static final int tableSizeFor(int c) {//返回一个大于输入参数且最小的为2的n次幂的数。
     int n = c - 1;
     n |= n >>> 1;
     n |= n >>> 2;
     n |= n >>> 4;
     n |= n >>> 8;
     n |= n >>> 16;
     return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
 }

tableSizeFor(int c)的原理:将c最高位以下通过|=运算全部变成1,最后返回的时候,返回n+1;
eg:当输入为25的时候,n等于24,转成二进制为1100,右移1位为0110,将1100与0110进行或("|")操作,得到1110。接下来右移两位得11,再进行或操作得1111,接下来操作n的值就不会变化了。最后返回的时候,返回n+1,也就是10000,十进制为32。按照这种逻辑得到2的n次幂的数。

table初始化

table的初始化操作会延缓到第一次put行为。

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)//判断table还未初始化
            tab = initTable();//初始化table
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;                   // no lock when adding to empty bin
        }
       ...省略一部分源码
    }
} 

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
    //如果一个线程发现sizeCtl<0,意味着另外的线程执行CAS操作成功,当前线程只需要让出cpu时间片,
    //由于sizeCtl是volatile的,保证了顺序性和可见性
        if ((sc = sizeCtl) < 0)//sc保存了sizeCtl的值
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {//cas操作判断并置为-1
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;//DEFAULT_CAPACITY = 16,若没有参数则大小默认为16
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}  
put操作
    
 final V putVal(K key, V value, boolean onlyIfAbsent) {
     if (key == null || value == null) throw new NullPointerException();
     int hash = spread(key.hashCode());//哈希算法
     int binCount = 0;
     for (Node<K,V>[] tab = table;;) {//无限循环,确保插入成功
         Node<K,V> f; int n, i, fh; K fk; V fv;
         if (tab == null || (n = tab.length) == 0)//表为空或表长度为0
             tab = initTable();//初始化表
         else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//i = (n - 1) & hash为索引值,查找该元素,
         //如果为null,说明第一次插入
             if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                 break;                   // no lock when adding to empty bin
         }
         else if ((fh = f.hash) == MOVED)//MOVED=-1;当前正在扩容,一起进行扩容操作
             tab = helpTransfer(tab, f);
         else if (onlyIfAbsent && fh == hash &&  // check first node
                  ((fk = f.key) == key || fk != null && key.equals(fk)) &&
                  (fv = f.val) != null)
             return fv;
         else {
             V oldVal = null;
             synchronized (f) {//其他情况加锁同步
                 if (tabAt(tab, i) == f) {
                     if (fh >= 0) {
                         binCount = 1;
                         for (Node<K,V> e = f;; ++binCount) {
                             K ek;
                             if (e.hash == hash &&
                                 ((ek = e.key) == key ||
                                  (ek != null && key.equals(ek)))) {
                                 oldVal = e.val;
                                 if (!onlyIfAbsent)
                                     e.val = value;
                                 break;
                             }
                             Node<K,V> pred = e;
                             if ((e = e.next) == null) {
                                 pred.next = new Node<K,V>(hash, key, value);
                                 break;
                             }
                         }
                     }
                     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;
                         }
                     }
                     else if (f instanceof ReservationNode)
                         throw new IllegalStateException("Recursive update");
                 }
             }
             if (binCount != 0) {
                 if (binCount >= TREEIFY_THRESHOLD)
                     treeifyBin(tab, i);
                 if (oldVal != null)
                     return oldVal;
                 break;
             }
         }
     }
     addCount(1L, binCount);
     return null;
 }
 //哈希算法
 static final int spread(int h) {
     return (h ^ (h >>> 16)) & HASH_BITS;
 }
 //保证拿到最新的数据
 static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
     return (Node<K,V>)U.getObjectAcquire(tab, ((long)i << ASHIFT) + ABASE);
 }
 //CAS操作插入节点,比较数组下标为i的节点是否为c,若是,用v交换,否则不操作。
 //如果CAS成功,表示插入成功,结束循环进行addCount(1L, binCount)看是否需要扩容
 static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                     Node<K,V> c, Node<K,V> v) {
     return U.compareAndSetObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
 }
table扩容

当table的容量不足的时候,即table的元素数量达到容量阈值,需要对table进行扩容。扩容分为两部分:
1、构建一个nextTable,大小为table的两部分。
2、把table的数据复制到nextTable中。

private final void addCount(long x, int check) {
    ... 省略部分代码
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            if (sc < 0) {// sc < 0 表明此时有别的线程正在进行扩容
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                // 不满足前面5个条件时,尝试参与此次扩容,把正在执行transfer任务的线程数加1,+2代表有1个,+1代表有0个
                    transfer(tab, nt);
            }
            //试着让自己成为第一个执行transfer任务的线程
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);// 去执行transfer任务
            s = sumCount();// 重新计数,判断是否需要开启下一轮扩容
        }
    }
}
get操作

1、判断table是否为空,如果为空,直接返回null。
计算key的hash值,并获取指定table中指定位置的Node节点,通过遍历链表或树结构找到对应的节点,返回value值。

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

参考地址:
https://www.jianshu.com/p/865c813f2726

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值