ConcurrentHashmap与HashTable

HashTable原理:
Hashtable相当于线程安全的hashmap,其用synchronized保证线程安全性。在hashtable中,put和remove都是同步方法,在put时就不能进行remove操作。因此在线程竞争激烈的情况下,其效率非常低下。ConcurrentHashmap便对此进行了优化。
ConcurrentHashMap原理:
ConcurrentHashMap使用了锁分段技术,即首先将数据分成一段一段的存储,然后每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap是由Segment数组和HashEntry数据结构组成,segment是一种可重入锁ReentranLock,HashEntry用于存储键值对,一个ConcurrentHashMap里包含一个Segment数组,segment结构与hashmap结构类似,是一种数组和链表结构,一个segment里包含一个hashentry数组,每个hashentry是一个链表结构的元素,每个segment守护着一个hashentry数组里的元素,当对hashentry元素进行修改时,必须先获得他所对应的segment锁。
结构如下:
在这里插入图片描述
两者put比较:
HashTable的put方法:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}
从代码中可以看出HashTable的put方法都用了synchronized关键字实现同步,并且key值不为空。这样相当于每次进行put 的时候都会进行同步,当10个线程同步进行操作的时候,就会发现当第一个线程进去,其他线程必须等待第一个线程执行完成,才可以进行下去。性能特别差。

ConcurrentHashMap的put方法如下:

public V put(K key, V value) { 
    Segment<K,V> s; 
    if (value == null) 
      throw new NullPointerException(); 
    int hash = hash(key);**//根据散列函数,计算出key值的散列值** 
    int j = (hash >>> segmentShift) & segmentMask;//定位Segment的数组下标
    if ((s = (Segment<K,V>)UNSAFE.getObject     
       (segments, (j << SSHIFT) + SBASE)) == null) 
     s = ensureSegment(j);
 return s.put(key, hash, value, false); 
 } 

segment的put方法:

  final V put(K key, int hash, V value, boolean onlyIfAbsent) { 
        HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value)    ;//非阻塞获取锁
        V oldValue; 
        try { 
          HashEntry<K,V>[] tab = table;//Segment对应的HashEntry数组长度 
         int index = (tab.length - 1) & hash; 
         HashEntry<K,V> first = entryAt(tab, index);//获取HashEntry数组的第一个值 
         for (HashEntry<K,V> e = first;;) { 
           if (e != null) {//HashEntry数组已经存在值 
             K k; 
             if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {  //key值和hash值都相等,则直接替换旧值 
              oldValue = e.value; 
               if (!onlyIfAbsent) { 
                 e.value = value; 
                 ++modCount; 
               } 
               break; 
             } 
             e = e.next;//不是同一个值则继续遍历,直到找到相等的key值或者为null的HashEntry数组元素 
           } 
           else {//HashEntry数组中的某个位置元素为null 
             if (node != null) 
               node.setNext(first);//将新加入节点(key)的next引用指向HashEntry数组第一个元素 
             else//已经获取到了Segment锁 
               node = new HashEntry<K,V>(hash, key, value, first) 
             int c = count + 1; 
             if (c > threshold && tab.lenth < MAXIUM_CAPACITY)//插入前先判断是否扩容,ConcurrentHashMap扩容与HashMap不同,ConcurrentHashMap只扩Segment的容量,HashMap则是整个扩容 
               rehash(node); 
             else 
               setEntryAt(tab, index, node);//设置为头节点 
             ++modCount;//总容量 
             count = c; 
             oldValue = null; 
             break; 
           } 
          } 
       } finally { 
         unlock(); 
       } 
       return oldValue; 
     } 

上述代码可看出,segment里面才是真正的hashtable,这里需要找出entry在table中的那个位置,之后得到的entry就是这个链中的第一个节点,如果e!=null,说明找到了,这时就要替换节点中的值,否则我们需要new一个node,将新的entry插到链表头节点。
put方法首先定位到segment,然后在segment里面进行插入操作。插入操作需要两个步骤,第一判断是否需要对segment里面的hashentry数组进行扩容,第二步找到元素添加的位置然后放到hashentry数组中。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值