9、 ConcurrentHashMap的线程安全

HashMap没有考虑并发处理,比如多个线程put的时候,如果key的hash值一样,可能导致数据的覆盖丢失。如果非常巧合,一个线程put,另外一个线程扩容需要移动元素,对资源的引用发送了冲突,就会发送死锁。

       同时还在扩容的时候也会存在问题,比如多个线程,第一个线程正在put,第二个线程也是正在put,第三个线程发现table容量不够用,需要扩容了,此时就会乱套,导致死循环。

       举例:

       第一个线程put, key为a1, value为b1,完成。

       第二个线程put, key为a2, value为b2, next为a1元素,cpu切换;

       第三个线程put,table的size超过阀值(12), 开始扩容两倍,扩容的时候,元素的顺序,根与叶进行了颠倒,这样形成了元素a1的next为a2.

       这时a2元素继续执行,a2设置next为a1,这时就形成了死循环。

可以看下几个方法:transfer、createEntry(hash, key, value,bucketIndex);


那ConcurrentHashMap为什么是线程安全的?

ConcurrentHashMap用到了很多Unsafe的原子性操作方法,内存操作,锁操作,从底层保证数据的原子性、可见性、有序性。

首先ConcurrentHashMap通过key的hash值得到对于的segment

s= ensureSegment(j);

        return s.put(key, hash, value, false);

然后在对于的segment进行put操作,这时每个单独的segment的put是上了锁的

 HashEntrynode = tryLock() ? null:

                scanAndLockForPut(key, hash, value);

   这样,在独立的segment段内进行操作,是可以保证线程安全的。


   ConcurrentHashMap中用到了Unsafe很多方法,比较常见及重要的有:

[if !supportLists]1、 [endif]UNSAFE.getObject,是从指定的内存偏移量地址中获取到对象

[if !supportLists]2、 [endif]UNSAFE.getObjectVolatile, 可以直接获取指定内存的数据,保证了每次拿到数据都是最新的。可见性。

[if !supportLists]3、 [endif]UNSAFE.compareAndSwapObject,比较并更新对象的某一个对象类型的域,1被操作的对象,fieldoffset 被操作的域在对象中的偏移量,expect 域的期望值,update 域的更新值 。


以上主要是针对JDK7进行说明的, JDK8改动太大,不在本文档说明范围内。


i

你可能感兴趣的:(9、 ConcurrentHashMap的线程安全)