ConcurrentHashMap简单分析 - wtstengshen/blog-page GitHub Wiki

####1,ConcurrentHashMap简单结构图 ConcurrentHashMap 1,ConcurrentHashMap首先分成了很多个Segment,在构造方法中可以进行指定,到底需要多少个Segment,Segment的个数其实就是构造方法参数的concurrencyLevel默认值是16; 2,Segment继承了ReentrantLock,ReentrantLock提供了加锁和解锁的操作,并发环境中一旦出现多线程修改临界区的情况,不管使用是CAS还是直接synchronized加锁,目的是为了防止并发修改出现问题,只不过是为了减少锁的力度; 3,Segment中包含了HashEntry对象的数组,用于真正存放Map中的数据; 4,HashEntry对象有next指针,用于解决hash碰撞之后,使用链表的方式存储数据;

####2,引入Segment的目的是什么? 引入segment的目的是为了在多线程并发操作的过程中,减少锁的粒度,这里的粒度的意思是:如果多个线程,修改的Map中的key,不在同一个Segment当中,那么他们之间是可以进行并发修改操作,没有临界区的问题;如果多个线程,同时修改同一个Segment中的数据,那么还是要进行加锁的操作的;其实引入Segment就是把临界区再次进行拆分,减少多线程相互等待加锁的粒度; 可以分析一下put方法的源码:

//摘选了一部分代码
    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        // 注释(1)
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null) 
            s = ensureSegment(j);
         // 注释(2)
        return s.put(key, hash, value, false);
    }

1,注释(1)中的代码,通过hash运行定位到一个Segment; 2,注释(2)中的代码,Map的put操作其实是Segment的put操作; ConcurrentHashMap首先根据put方法的key,hash计算出需要进行修改的Segment是哪一个,然后对这个Segment调用对应的方法;而不是直接去把需要更改数据槽进行加锁;这样的目的就是为了减少加锁的粒度,如果多个线程分别修改的是不同的segment里的内容,那么这几个线程之间就不存在加锁的竞争关系; ####3,ConcurrentHashMap数据一致性?

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

1,ConcurrentHashMap的get方法,在整个过程中没有进行任何加锁操作; 2,get方法的过程是:首先定位到Segment,然后对HashEntry数组进行遍历操作,根据key找到对应的value; 3,在get方法执行过程中,并没有对定位到的Segment进行加锁操作,这就意味着,在get方法执行的过程中,Segment里的数据有可能发生急剧的变化,有可能获取到了数据,但是下一个cpu周期,数据就有可能没有了;但是想想这样设计的原因是因为ConcurrentHashMap就是为了给并发环境设计的,在多线程并发环境中,确实会出现获取的数据,下一时刻就被删除了,所以我认为ConcurrentHashMap的get操作是弱一致性的设置,这也符合实际的高并发情况;

####4,写在最后 ConcurrentHashMap能够保证在并发环境下多线程操作的问题,比HashTale的效率要高;其实我认为,能不进行并发操作的,就不要进行并发操作,实在是没有办法在设计上消除的,就在多线程并发的时候,降低锁的粒度,就是减少锁的范围;为什么ConcurrentHashMap的并发性能要比HashTable高呢,看一下HashTable的方法就会发现,HashTable所有的可能出现并发问题的方法都被synchronized修饰了,synchronized加锁的时候都是对整个对象进行的加锁,而不是ConcurrentHashMap的对对象中的Segment进行加锁。文章里的术语,有可能表达的不准确的,请拍砖;

⚠️ **GitHub.com Fallback** ⚠️