ConcurrentHashMap详解及ConcurrentHashMap和HashMap、HashTable的区别

一、ConcurrentHashMap

jdk1.7源码分析ConcurrentHashMap:(在源码中可以看到自jdk1.5开始引入ConcurrentHashMap)
ConcurrentHashMap是对HashMap线程不安全的优化,使其线程安全且高效。下面对源码的一些内容做以分析:
ConcurrentHashMap详解及ConcurrentHashMap和HashMap、HashTable的区别

1、继承关系

(jdk1.5开始)继承了AbstractMap、实现了ConcurrentMap和序列化接口

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable 

2、底层数据结构

数组+数组+链表
ConcurrentHashMap源码中包含三个主要的内部类:

  1. Holder
  2. HashEntry
  3. Segment

在Holder类的属性中:有一个Segment<K,V>类型的数组:segments

final Segment<K,V>[] segments;

ConcurrentHashMap详解及ConcurrentHashMap和HashMap、HashTable的区别
HashEntry实现了ConcurrentHashMap底层的链表结构
ConcurrentHashMap详解及ConcurrentHashMap和HashMap、HashTable的区别
Segment类中定义了hashEntry类型的table数组
ConcurrentHashMap详解及ConcurrentHashMap和HashMap、HashTable的区别
基于上面的表述:就可以将ConcurrentHashMap的具体结构画出来
ConcurrentHashMap详解及ConcurrentHashMap和HashMap、HashTable的区别

3、基本属性和默认值

  • 默认初始容量:作用于hashEntry的table属性
 static final int DEFAULT_INITIAL_CAPACITY = 16;
  • 默认加载因子:作用于HashEntry
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • 默认并发度:作用于Segment数组大小
    (并发度:能同时操作集合的线程最大数量)
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  • 最大容量:作用于segment中数组的最大容量 table.length < MAXIMUM_CAPACITY
  static final int MAXIMUM_CAPACITY = 1 << 30;
  • 每个分段的最小容量: 作用于hashEntry类型的table数组
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
  • 分段最大容量(最大并发度)
 static final int MAX_SEGMENTS = 1 << 16;
  • 默认自旋次数(超过这个次数直接加锁)
 static final int RETRIES_BEFORE_LOCK = 2;

4、构造函数(5个)

  1. 指定初始容量16、加载因子0.75和并发度16
  public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) 
  1. 指定初始容量、加载因子0.75
public ConcurrentHashMap(int initialCapacity, float loadFactor) 
  1. 指定初始容量 :创建一个带有指定初始容量、默认加载因子 (0.75) 和并发度 (16) 的新的空映射
    public ConcurrentHashMap(int initialCapacity) 
  1. 无参构造: 创建一个带有默认初始容量 (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
 public ConcurrentHashMap()
  1. 通过Map转化:构造一个与给定映射具有相同映射关系的新映射
 public ConcurrentHashMap(Map<? extends K, ? extends V> m)

5、扩容

扩容只针对ConcurrentHashMap中的table数组进行扩容。
方法在Segment类中:rehash() , 扩容倍数为:2倍扩容

6、常用方法

  • boolean isEmpty() //判断集合是否为空
  • int size() //返回当前集合的键值对的个数
  • V get(Object key) //通过当前的key来获取value
  • boolean containsKey(Object key) //判断当前集合是否包含key
  • boolean containsValue(Object value) //判断当前集合是否包含value
  • V put(K key, V value) //添加元素
  • V remove(Object key) //删除元素
  • boolean remove(Object key, Object value) //删除元素

7、put()方法研究

ConcurrentHashMap的put()方法

   public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)  //key和value不能为null
            throw new NullPointerException();
        int hash = hash(key);  //通过key值进行哈希
        int j = (hash >>> segmentShift) & segmentMask;
        //找到key对应的segment数组的索引位置,
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
            //segment内部的put操作
        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;
				//hash到数组中对应的位置
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);				
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            //相等情况下,onlyIfAbsent默认false,当key相等时,返回旧的value的值,并将旧value值替换成新的value, 当onlyIfAbsent为true时,直接返回旧的value
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();  //释放锁
            }
            return oldValue;
        }

二、ConcurrentHashMap和HashMap、HashTable的区别

1、hashtable和concurrentHashMap线程安全保证机制是否一样?

!!!不一样

  • HashTable保证线程安全的机制
    HashTable中的方法添加有Syncnized关键字,该关键字是一个互斥锁。目的是同一时刻只能有一个线程对资源进行访问。
    在HashTable中,对put,get等一般方法添加Syncnized关键字,修饰的是类对象,该对象调用put操作,即为该对象添加了一个互斥锁,同时只能有一个线程访问hashtable,从而保证添加元素不会出现异常

  • ConcurrentHashMap保证线程安全的机制
    ConcurrentHashMap是将整个集合细分为多个Segment保存在segments[]数组中,Segment继承了ReentrantLock锁,就拥有了它的锁机制,就表示只有在同一个Segment内的线程在属于竞争关系,而在不同的Segment之间的线程,不会存在竞争关系。
    Segment内部有一个HashEntry类型的table数组,数组中hash到同一个位置的元素会通过链表将数据加在链表后,table[]数组用volatile关键字修饰,可以保证在table数组中同一时刻只能有一个线程访问数组,

2、HashMap和HashTable和concurrentHashMap异同点?

相同点
1.底层数据结构相同,都为数组+链表( ConcurrentHashMap为数组+数组+链表)
2.key都不能重复
3.插入元素不能保证有序
4.都是通过key进行哈希

不同点
1.安全性问题
HashMap是非线程安全的集合,若想让其在多线程下具有安全性:使用集合工具类collection.SyncnizedMap;
HashTable中的方法都有Synchronized修饰,多线程访问时线程安全;
ConcurrentHashMap中通过分段锁机制保证线程安全
2.继承关系:
hashMap和ConcurrentHashMap继承AbstractMap
hashTable继承Dictionary
3.扩容方式:
HashMap和 ConcurrentHashMap为2倍(2table.length)
HashTable为2的倍数+1 [(2
table.length)+1]
4.null值:
HashTable的键值不能为null
hashMap键值可以为null
ConcurrentHashMap键可以为null,值不能为null。
5.默认值:
hashTable数组默认大小11
hashMap数组默认大小16
ConcurrentHashMap数组默认大小16
6.hash算法不同:
7.效率不同:
hashMap在单线程下效率高
hashTable和ConcurrentHashMap在多线程下效率高(ConcurrentHashMap更高)