ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同

Posted 牛牛最爱喝兽奶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同相关的知识,希望对你有一定的参考价值。

ConcurrentHashMap


ConcurrentHashMap在与HashMap的不同之处在于多线程下更安全,而针对于Hashtable的基础上进行了一次升级。Hashtable的安全机制过于粗暴,导致性能很低。根据不同版本采用的方式也有不同。

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

1.7版本的ConcurrentHashMap

主要采用的是分段锁机制,并且读取时不进行加锁操作。底层的结构还是和1.7版本的hashmap类似,只是多了一个Segments数组,一个Segment可以看作类似的hashtable,里面包含了一个table数组用来存取hashentry,table里每个元素代表着链表的头节点,transient表示该变量不进行序列化,继承了ReentrantLock 重入锁。

static final class Segment<K,V> extends ReentrantLock implements Serializable  
    transient volatile int count; 
    transient int modCount; 
    transient int threshold; 
    transient volatile HashEntry<K,V>[] table; 
    final float loadFactor; 

HashEntry的声明类,用于数据存取的对象

static final class HashEntry<K,V>  
    final K key; //键
    final int hash; //hash值hashcode(k)^hashcode(v)
    volatile V value; 
    final HashEntry<K,V> next; //final修饰的变量只能被初始化一次

ConcurrentHashMap的实现:

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; 
    int ssize = 1; 
    while (ssize < concurrencyLevel)  
        ++sshift; 
        ssize <<= 1; 
     
    segmentShift = 32 - sshift; 
    segmentMask = ssize - 1; 
    this.segments = Segment.newArray(ssize); 
    if (initialCapacity > MAXIMUM_CAPACITY) 
        initialCapacity = MAXIMUM_CAPACITY; 
    int c = initialCapacity / ssize; 
    if (c * ssize < initialCapacity) 
        ++c; 
    int cap = 1; 
    while (cap < c) 
        cap <<= 1; 
    for (int i = 0; i < this.segments.length; ++i) 
        this.segments[i] = new Segment<K,V>(cap, loadFactor); 

获取数据V不需要进行加锁,首先找到哪一个segment,然后确定table[i]数组中的位置,在进行遍历链表,取值。

1 public V get(Object key)  
2     int hash = hash(key.hashCode()); //采用二次hash减少hash冲突
3     return segmentFor(hash).get(key, hash); 
4 
final Segment<K,V> segmentFor(int hash)  
     return segments[(hash >>> segmentShift) & segmentMask]; 

 1 V get(Object key, int hash)  //get方法
 2     if (count != 0)  // read-volatile // ①
 3         HashEntry<K,V> e = getFirst(hash); 获取table里的元素
 4         while (e != null)  
 5             if (e.hash == hash && key.equals(e.key))  
 6                 V v = e.value; 
 7                 if (v != null) // ② 注意这里
 8                     return v; 
 9                 return readValueUnderLock(e); // recheck 
10              
11             e = e.next; 
12          
13      
14     return null; 
15 

在这里需要注意的是next指向下一个结点被final关键字修饰,也就是说不能在直接进行插入或者删除的工作,而且添加元素也只能在头部进行插入。当我们正在查询时,此时进行了添加工作,应该优先进行添加工作,happen-before原则。这样能保证我们读取的值是最新的。在创建entry时会发现很多变量都被volatile和final修饰,volatile主要是保证线程的有序性和可见性。
put操作源码

 1 V put(K key, int hash, V value, boolean onlyIfAbsent)  
 2     lock(); //获取锁的对象
 3     try  
 4         int c = count; 
 5         if (c++ > threshold) // ensure capacity 扩容
 6             rehash(); 
 7         HashEntry<K,V>[] tab = table; 
 8         int index = hash & (tab.length - 1); //获取数组下标值
 9         HashEntry<K,V> first = tab[index]; //数组中第一个entry对象
10         HashEntry<K,V> e = first; 
11         while (e != null && (e.hash != hash || !key.equals(e.key))) 
12             e = e.next; 
13    
14         V oldValue; 
15         if (e != null)  //表示数组已经存在该KEY
16             oldValue = e.value; 
17             if (!onlyIfAbsent) //表示是否要覆盖
18                 e.value = value; 
19          
20         else  //不存在key,
21             oldValue = null; 
22             ++modCount; 
23             tab[index] = new HashEntry<K,V>(key, hash, first, value); 
24             count = c; // write-volatile 
25          
26         return oldValue; 
27      finally  
28         unlock(); 
29      
30 

remove操作详情

 1 V remove(Object key, int hash, Object value)  
 2     lock(); //获取锁对象
 3     try  
 4         int c = count - 1; 
 5         HashEntry<K,V>[] tab = table; 
 6         int index = hash & (tab.length - 1); 
 7         HashEntry<K,V> first = tab[index]; 
 8         HashEntry<K,V> e = first; 
 9         while (e != null && (e.hash != hash || !key.equals(e.key))) 
10             e = e.next; 
11    
12         V oldValue = null; 
13         if (e != null)  
14             V v = e.value; 
15             if (value == null || value.equals(v))  
16                 oldValue = v; 
17                 // All entries following removed node can stay 
18                 // in list, but all preceding ones need to be 
19                 // cloned. 
20                 ++modCount; 
21                 HashEntry<K,V> newFirst = e.next; 
22                 for (HashEntry<K,V> p = first; p != e; p = p.next) 
23                     newFirst = new HashEntry<K,V>(p.key, p.hash, 
24                                                   newFirst, p.value); //方法里的newFirst表示next指针。
25                 tab[index] = newFirst; 
26                 count = c; // write-volatile 
27              
28          
29         return oldValue; 
30      finally  
31         unlock(); //解锁释放锁的对象
32      
33 

由于删除之后表结构会发生该表,会将删除结点之前的元素进行一个倒叙排列,最后再将结点之后的元素连接起来。

ConcurrentHashMap1.8

ConcurrentHashMap1.8底层采用的数据结构是数组+链表+红黑树,采用的synchronization+CAS实现多线程安全机制。
Node结点代码里只有find并没有其他方法!

 static class Node<K,V> implements Map.Entry<K,V> 
        final int hash;
        final K key;
        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; 
        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)));
        

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        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;
        
    

一些compareAndSwapObject(CAS)操作,保证了原子性

    @SuppressWarnings("unchecked")
    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) 
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    

    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);
    

    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) 
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    

定义了一些变量

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

    /**
     * The next table to use; non-null only while resizing.
     */
    private transient volatile Node<K,V>[] nextTable;

    /**
     * Base counter value, used mainly when there is no contention,
     * but also as a fallback during table initialization
     * races. Updated via CAS.
     */
    private transient volatile long baseCount;

    /**
     * Table initialization and resizing control.  When negative, the
     * table is being initialized or resized: -1 for initialization,
     * else -(1 + the number of active resizing threads).  Otherwise,
     * when table is null, holds the initial table size to use upon
     * creation, or 0 for default. After initialization, holds the
     * next element count value upon which to resize the table.
     */
    private transient volatile int sizeCtl;

    /**
     * The next table index (plus one) to split while resizing.
     */
    private transient volatile int transferIndex;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
     */
    private transient volatile int cellsBusy;

    /**
     * Table of counter cells. When non-null, size is a power of 2.
     */
    private transient volatile CounterCell[] counterCells;

    // views
    private transient KeySetView<K,V> keySet;
    private transient ValuesView<K,V> values;
    private transient EntrySetView<K,V> entrySet;

get方法,并没有进行加锁,支持高并发

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 

以上是关于ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同的主要内容,如果未能解决你的问题,请参考以下文章

ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同

ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同

ConcurrentHashMap1.7和1.8对比

ConcurrentHashMap1.7和1.8对比

翻了ConcurrentHashMap1.7 和1.8的源码,我总结了它们的主要区别。

19 ConcurrentHashMap1.7