Java后端开发面试的时候,一场好的面试,是无论如何也绕不开并发编程的。并发编程里面往往有个很重要的类可能会被拿出来探讨:ConcurrentHashMap。
ConcurrentHashMap是HashMap的线程安全版。大家都知道HashMap的高性能,但是HashMap不是线程安全的。所以为了解决这个问题,就推出了Concurrent版本。
在和面试官探讨这个问题的时候,大家一般都会把分段锁Segment的实现原理拿出来大讲特讲(因为除了这个分段式锁外,好像别的也没有什么不同的)。
面试官:请简述下ConcurrentHashMap的实现原理。
面试者:
1、ConcurrentHashMap是由Segment数组和HashEntry数组组成。它内部细分成了若干个小的HashMap,每个小的HashMap被称为段(Segment),默认情况下,一个ConcurrentHashMap被细分为16个Segment。
2、Segment是一种可重入锁ReentrantLock,它在ConcurrentHashMap中扮演锁的角色;HashEntry用来存储键值对数据。
3、一个 ConcurrentHashMap 里包含一个 Segment 数组,Segment 的结构和 HashMap类似,是一种数组+链表结构。
4、每个Segment里包含一个HashEntry数组,当要对HashEntry数组的数据进行修改时,必须要先获得它对应的Segment锁。
此时,是不是感觉自己答的很溜?这个问题中的重要点都回答了。
但是,这真的是你用的ConcurrentHashMap吗?还是你Out了?
其实,你还真是Out了。上面的回答是没问题的,但是知识点Out了。这个ConcurrentHashMap分段锁的实现是JDK1.7的了(网上的很多知识都是这样的)。JDK1.8之后的实现有了很大的改变了
说多了浪费口水,还是直接看源码吧。
ConcurrentHashMap代码在java.util.concurrent包中(不要搞错了)
public class ConcurrentHashMap extends AbstractMapimplements ConcurrentMap, Serializable {/* ---------------- Constants -------------- *///... 省略部分代码//实现重点代码/* ---------------- Nodes -------------- *//*** Key-value entry. This class is never exported out as a* user-mutable Map.Entry (i.e., one supporting setValue; see* MapEntry below), but can be used for read-only traversals used* in bulk tasks. Subclasses of Node with a negative hash field* are special, and contain null keys and values (but are never* exported). Otherwise, keys and vals are never null.*/static class Node implements Map.Entry {final int hash;final K key;volatile V val;volatile Node next;Node(int hash, K key, V val, Node 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 find(int h, Object k) {Node 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;}}//... 省略部分代码
我们会发现,ConcurrentHashMap类中中没有找到Segment的实现的影子。而是在类中发现了很多使synchronized的方式来实现的。
我们看下常用的put方法的实现。发现使用的是synchronized来实现的。
public V put(K key, V value) {return putVal(key, value, false);}/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node[] tab = table;;) {Node f; int n, i, fh;if (tab == null || (n = tab.length) == 0)tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null,new Node(hash, key, value, null)))break; // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {V oldVal = null;synchronized (f) {if (casTabAt(tab, i, null, r)) {//...省略代码}
···
static final boolean casTabAt(Node[] tab, int i,Node c, Node v) {return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);}
新版的ConcurrentHashMap和Segment分段锁没有任何关系。它的实现方式和HashMap有点大同小异:数组+链表+红黑树。
类型 | JDK1.7 | JDK1.8 |
---|---|---|
数据结构 | Segment分段锁 | 数组+链表+红黑树 |
线程安全机制 | segment的分段锁机制 | CAS+Synchorized机制 |
锁的粒度 | 每段Segment加锁,粒度大 | 每个Node元素加锁,粒度更细 |
遍历时间复杂度 | O(n) | O(logN) |
好了,如果你把上面新的知识结合老的知识一起给到面试官,我想面试官肯定会想,这家伙是个可造之才,让HR通知他,明天赶紧来上班。 |