JAVA之ConcurrentHashMap源码深度分析
来源:互联网 发布:立项依据怎么写 知乎 编辑:程序博客网 时间:2024/03/28 19:38
欢迎转载,请附上出处:
http://blog.csdn.net/as02446418/article/details/47004573
我们首先来看一下ConcurrentHashMap类的声明:
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable
其中,这个类继承了java.util.AbstractMap中已有的实现,这个在前面整理HashMap的时候已经提过了,重点看后面实现的接口ConcurrentMap和Serializable。Serializable是做序列化处理的,而ConcurrentMap的定义又如下:
public interface ConcurrentMap<K, V> extends Map<K, V> { V putIfAbsent(K key, V value); boolean remove(Object key, Object value); boolean replace(K key, V oldValue, V newValue); V replace(K key, V value);}
其中规定了4个方法。
V putIfAbsent(K key, V value); 如果没有这个key,则放入这个key-value,返回null,否则返回key对应的value。
boolean remove(Object key, Object value); 移除key和对应的value,如果key对应的不是value,移除失败
boolean replace(K key, V oldValue, V newValue); 替代key对应的值,仅当当前值为旧值
V replace(K key, V value); 替代key对应的值,只要当前有值
这些方法都在ConcurrentHashMap实现了,后文会部分提到这些。
1. 构造方法和ConcurrentHashMap的Segment实现
先看下构造方法:
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
有几个重载的方法,说这个参数最全的,有3个参数,除了HashMap中涉及到的loadFactor和initialCapacity外,还有一个concurrencyLevel,翻译过来就是并发级别或者并发度。
与此对应,ConcurrentHashMap中有一个segments数组对象,元素类型是ConcurrentHashMap的内部类Segment,而concurrencyLevel就是这个segments数组的大小。
我们来看下这个Segment类:
static final class Segment<K,V> extends ReentrantLock implements Serializable
Segment扩展了ReentrantLock并实现了Serializable接口。除此之外,我们还发现这个类里实现的东西和java.util.HashMap非常相似。
实际上,这个类正是整个ConcurrentHashMap实现的关键。我想,作为这篇文章读者的您,应该会用到过各式各样的数据库,就拿Mysql的innoDB引擎来看,它除了支持表级锁意外,还支持行级锁,意义就在于这减小了锁粒度,当只对某行数据进行操作的时候,很可能没有必要限制同一个表中其它行的数据。在这个类中,这个Segment也是起到了同样的作用。每个Segment本身就是一个ReentrantLock,只有要修改的数据存在在同一个Segment,才有可能会需要锁定,这样就提高了多线程情况下效率,没必要所有线程全部等待锁。
2. get()方法源码分析
我们先看下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;}
实际上,就是通过key计算得到的hash值,确定对应的Segment对象,并用原子操作获取到对应的table和table中hash值对应的对象。
我们可以看到,在这个过程中,是没有显式用到锁的,仅仅是通过Unsafe类和原子操作,避免了阻塞,提高了性能。
3. put()和putIfAbsent()方法分析
先看下put()方法的源码:
public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false);}
我们看到其中最后是使用Segment的put()方法的调用,而putIfAbsent()的方法的调用,仅仅是最后一个参数不同。
我们进一步看下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; 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; 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;}
和java.util.HashMap比起来,基本类似,有两个地方不同。一个是对onlyIfAbsent参数的判断处理。另一个则是对整个操作过程加锁,并在加锁的地方做了稍微巧妙的处理。就是在在等锁的过程中,不断的寻找和构建node节点对象,不管是否这个方法最终是否创建到了node节点,当这个方法返回时,一定是已经获得了这个Segment对应的锁。
而这个寻找和创建节点所在的循环,一方面是做节点的遍历查询,另一方面也是起到了自旋锁的作用,避免直接调用lock()而等锁阻塞,因为这样会在系统实现层面阻塞和唤醒线程,是有一定的切换成本的,对提高效率不利。
4. 综述
其实,纵观整个类的实现,都肯定会涉及到Segment的处理和其中方法的调用。对这些方法的调用,分为读操作和写处理,通常读操作没用用锁,而在修改操作,如remove() replace()等方法都和put()类似,在整个操作过程中对Segment进行了尝试加锁和自旋等锁的前提操作,并最后释放锁。这些写操作的等锁基本上和put()中的scanAndLockForPut()方法等同,除了需要创建节点。
从整体上看,由于Segment降低了并发中的锁粒度,并在写操作使用了锁。保证了整个ConcurrentHashMap的线程安全,也保证了并发运行时的效率。
5. 其它
在java.util.conccurent包中并没有TreeMap,但对于有序要求的容器,有基于skip list数据结构的Map实现ConcurrentSkipListMap,而且也有skip list的ConcurrentSkipListSet,和java.util包中的Set一样,是基于Map的。
- JAVA之ConcurrentHashMap源码深度分析
- JAVA源码分析-深度剖析ConcurrentHashMap
- java.util.concurrent 之ConcurrentHashMap 源码分析
- Java集合之ConcurrentHashMap源码分析
- ConcurrentHashMap之源码分析
- Java源码分析:ConcurrentHashMap
- Java-ConcurrentHashMap源码分析
- Java源码之ConcurrentHashMap
- Java源码之ConcurrentHashMap
- jdk源码分析之ConcurrentHashMap
- jdk源码分析之ConcurrentHashMap
- Java ConcurrentHashMap 源码分析(2)
- ConcurrentHashMap Java 8源码分析
- 我之见--java多线程 ConcurrentHashMap 源码分析
- JDK 1.7之 ConcurrentHashMap 源码分析
- JDK1.8源码分析之ConcurrentHashMap
- Java 8 中的ConcurrentHashMap源码分析
- Java集合: ConcurrentHashMap源码分析 JDK1.8
- AICL(Auto Input Current Limited)
- 《深入浅出struts2》--第四章,OGNL
- QListWidget实现图片缩略图形式的列表
- Day02笔记
- Extjs4 源码分析系列一 类的创建过程
- JAVA之ConcurrentHashMap源码深度分析
- ant.xml文件
- C# 属性和字段 get set
- 分布式网站架构后续:zookeeper技术浅析
- drools入门(二)-----规则引擎Drools解决汽水问题(复杂逻辑)
- LeetCode 8 String to Integer (atoi)
- python getopt使用
- ALM损坏后的恢复步骤
- 初次使用keepalived应该注意的