Concurrent HashMap的源码分析

Concurrent HashMap的特点:线程安全
1、Concurrent HashMap的底层数据结构?
数组(Segment)+数组(table)+链表(hashEntry)(Segment(初始化后不可扩容)+table(可扩容)+链表)
通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

  • ConcurrentHashMap、Segment、HashEntry(节点)
    Concurrent HashMap的源码分析_第1张图片

2、HashMap、HashTable和ConcurrentHashMap区别是什么?
(1)安全性问题:
HashMap不能保证线程安全
HashTable能保证线程
Concurrent HashMap能保证线程安全
(2)继承关系:
HashMap继承自AbstractMap
HashTable继承自Dictionary
Concurrent HashMap继承自AbstractMap
(3)null值问题
HashMap的key和value都可以为null
HashTable的key和value都不能为null
Concurrent HashMap的key和value都不能为null
(4)扩容方式
HashMap按照2table.length
HashTable按照2
table.length+1
Concurrent HashMap按照oldCapacity << 1

 HashEntry[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1;

(5)默认值
HashMap默认数组大小为16
HashTable默认数组大小为11
Concurrent HashMap默认数组为16

 static final int DEFAULT_INITIAL_CAPACITY = 16;

(6)效率不同
HashMap在单线程小效率高
HashTable在单线程小效率低
ConcurrentHashMap在多线程共享时
(7)锁的方式
Concurrent HashMap分段锁,完全允许多个读操作并发进行,读操作并不需要加锁。
Hash Table是对整张表进行加锁
HashMap 读操作需要加锁,不然就会得到不一致的数据.

  • 继承关系:
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable

Concurrent HashMap继承自AbstractMap,实现了ConcurrentMap接口,实现了Serializable表示Concurrent HashMap可以被序列化。

  • 属性
  // 表的最大容量
   private static final int MAXIMUM_CAPACITY = 1 << 30;
   // 默认表的大小
   private static final int DEFAULT_CAPACITY = 16;
      // 默认并发数
   private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
   // 装载因子
  static final float DEFAULT_LOAD_FACTOR = 0.75f;
     //默认Table数组的最小值
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
//默认segments的最大值
static final int MAX_SEGMENTS = 1 << 16;
  • 构造函数
//带参数的构造函数
//  创建一个带有默认初始容量  (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        //计算ssize左移的次数
        int ssize = 1;
        //计算segment的大小
                while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        //计算每个segment的大小
        if (c * ssize < initialCapacity)
            ++c;
            //若是条件成立,表示c有余数,所以增加一个segment
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        Segment s0 =
            new Segment(loadFactor, (int)(cap * loadFactor),
                             (HashEntry[])new HashEntry[cap]);
        Segment[] ss = (Segment[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

// 创建一个带有指定初始容量、加载因子和默认 concurrencyLevel (16) 的新的空映射。
  public ConcurrentHashMap(int initialCapacity, float loadFactor) {
        this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
    }

//创建一个带有指定初始容量、默认加载因子(0.75) 和 concurrencyLevel (16) 的新的空映射
 public ConcurrentHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }


 public ConcurrentHashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
    }

//    构造一个与给定映射具有相同映射关系的新映射
public ConcurrentHashMap(Map m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY),
             DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
        putAll(m);
    }

方法摘要:

 void clear():从该映射中移除所有映射关系 
 
boolean contains(Object value):一种遗留方法,测试此表中是否有一些与指定值存在映射关系的键。 
boolean containsKey(Object key): 测试指定对象是否为此表中的键。 
     
boolean containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回 true。 
     
numeration<V> elements():  返回此表中值的枚举。 
     
Set<Map.Entry<K,V>> entrySet(): 返回此映射所包含的映射关系的 Set 视图。 
     
v  get(Object key): 返回指定键所映射到的值,如果此映射不包含该键的映射关系,则返回 null。 
     
boolean  isEmpty():  如果此映射不包含键-值映射关系,则返回 true。 
     
Enumeration<K>  keys():   返回此表中键的枚举。 
     
Set<K>  keySet():   返回此映射中包含的键的 Set 视图。 
     
V   put(K key, V value):   将指定键映射到此表中的指定值。 
     
void  putAll(Map<? extends K,? extends V> m):  将指定映射中所有映射关系复制到此映射中。 
V  putIfAbsent(K key,V value):如果指定键已经不再与某个值相关联,则将它与给定值关联。 
     
V remove(Object key):  从此映射中移除键(及其相应的值)。 
     
boolean  remove(Object key, Object value): 只有目前将键的条目映射到给定值时,才移除该键的条目。 
     
V  replace(K key, V value):  只有目前将键的条目映射到某一值时,才替换该键的条目。 
     
boolean replace(K key, V oldValue, V newValue):  只有目前将键的条目映射到给定值时,才替换该键的条目。 
     
int  size():  返回此映射中的键-值映射关系数。 
     
Collection<V>  values():  返回此映射中包含的值的 Collection 视图
  • 方法:put()方法
    final V put(K key, int hash, V value, boolean onlyIfAbsent) {
        //每个segment操作进行put操作时都需要进行加锁操作
            HashEntry node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
                //尝试获取锁
            V oldValue;
            try {
                HashEntry[] tab = table;
       //table为segment连接的HashEntry数组
                int index = (tab.length - 1) & hash;
                //定位索引位置
                HashEntry first = entryAt(tab, index);
                //找到数组对应下标的链表
                for (HashEntry e = first;;) {
                    if (e != null) {
                    //头结点不为空
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            //当k相同时就将oldValue替换
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                             
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                           //添加新节点,添加在头部
                        else
                 node = new HashEntry(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;
        }
  • remove()方法:
final V remove(Object key, int hash, Object value) {
           if (!tryLock())
           //获取锁
               scanAndLock(key, hash);
           V oldValue = null;
           try {
               HashEntry<K,V>[] tab = table;
              //table是segment所连接的hashEntrt数组
               int index = (tab.length - 1) & hash;
               //定位索引位置
               HashEntry<K,V> e = entryAt(tab, index);
               //找到数组对应下标的链表头
               HashEntry<K,V> pred = null;
               //
               while (e != null) {
               //头结点不为空
                   K k;
                   HashEntry<K,V> next = e.next;
                   //继续向下hash
                   if ((k = e.key) == key ||
                       (e.hash == hash && key.equals(k))) {
                       //如果k的值相同且是索引位置的链表头,就将e的值赋值给v
                       V v = e.value;
                       if (value == null || value == v || value.equals(v)) {
                           if (pred == null)
                           //头结点为空
                               setEntryAt(tab, index, next);
                           else
                               pred.setNext(next);
                           ++modCount;
                           --count;
                           oldValue = v;
                       }
                       break;
                   }
                   pred = e;
                   e = next;
               }
           } finally {
               unlock();
           }
           return oldValue;
       }
  • get()方法:
public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

3、通过什么保证线程安全?
ReentrantLock.trylock()----->重入锁
Concurrent HashMap定义了三个原子操作,用于对指定位置的节点进行操作,在put()方法中使用;
4、HashTable和concurrentHashMap线程安全保证机制是否一样?
HashTable是通过synchronized来保证线程安全的,所有的线程要竞争同一把锁,也就是说:当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞状态,导致效率降低;
Concurrent HashMap是其分为不同的数据段,每个数据段都拥有一把锁,当多线程访问不同的数据段时,就不会发生线程竞争的情况,也就是说:当一个线程访问一个数据段的时候,其他线程可以访问其他数据段,不必抢夺,会优先的提高并发访问效率;

你可能感兴趣的:(总结)