ConcurrentHashmap与hashmap的区别是,concurrentHashMap是线程安全的,而hashmap是线程不安全;
通过学习ConcurrentHashmap的源码,让我进一步了解了其结构;
下面请看里面最常用的put方法; 源码如下:
@SuppressWarnings("unchecked")
public V put(K key, V value) {
1 Segment<K,V> s;
2 if (value == null)
3 throw new NullPointerException();
4 int hash = hash(key);
5 int j = (hash >>> segmentShift) & segmentMask;
6 if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
7 (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
8 s = ensureSegment(j);
9 return s.put(key, hash, value, false);
}
第4行代码获取到hash;第8行代码,获取到这个map所对应的segment;第9行代码,将put操作交代segment去做;
接着继续跟跟足赛segment的put方法;
1 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
2 HashEntry<K,V> node = tryLock() ? null :
3 scanAndLockForPut(key, hash, value);
4 V oldValue;
5 try {
6 HashEntry<K,V>[] tab = table;
7 int index = (tab.length - 1) & hash;
8 HashEntry<K,V> first = entryAt(tab, index);
9 for (HashEntry<K,V> e = first;;) {
10 if (e != null) {
11 K k;
12 if ((k = e.key) == key ||
13 (e.hash == hash && key.equals(k))) {
14 oldValue = e.value;
15 if (!onlyIfAbsent) {
16 e.value = value;
17 ++modCount;
18 }
19 break;
20 }
21 e = e.next;
22 }
23 else {
24 if (node != null)
25 node.setNext(first);
26 else
27 node = new HashEntry<K,V>(hash, key, value, first);
28 int c = count + 1;
29 if (c > threshold && tab.length < MAXIMUM_CAPACITY)
30 rehash(node);
31 else
32 setEntryAt(tab, index, node);
33 ++modCount;
34 count = c;
35 oldValue = null;
36 break;
37 }
38 }
39 } finally {
40 unlock();
41 }
42 return oldValue;
43 }
第1行代码:final代表方法不可被重写。第3行代码给整块代码段加锁;
第9行到39行代码执行业务代码,也就是进行put操作所要进行的,地址修改;由于我们现在研究的重点是加锁方式,
所以我们加锁的方法scanAndLockForPut;方法如下:
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
1 HashEntry<K,V> first = entryForHash(this, hash);
2 HashEntry<K,V> e = first;
3 HashEntry<K,V> node = null;
4 int retries = -1; // negative while locating node
5 while (!tryLock()) {
6 HashEntry<K,V> f; // to recheck first below
7 if (retries < 0) {
8 if (e == null) {
9 if (node == null) // speculatively create node
10 node = new HashEntry<K,V>(hash, key, value, null);
11 retries = 0;
12 }
13 else if (key.equals(e.key))
14 retries = 0;
15 else
16 e = e.next;
17 }
18 else if (++retries > MAX_SCAN_RETRIES) {
19 lock();
20 break;
21 }
22 else if ((retries & 1) == 0 &&
23 (f = entryForHash(this, hash)) != first) {
24 e = first = f; // re-traverse if entry changed
25 retries = -1;
26 }
27 }
28 return node;
29 }
第5行代码tryLock跟踪
1 public boolean tryLock() {
2 return sync.nonfairTryAcquire(1);
}
第2行代码跟踪,此处为非公平锁;
1 final boolean nonfairTryAcquire(int acquires) {
2 final Thread current = Thread.currentThread();
3 int c = getState();
4 if (c == 0) {
5 if (compareAndSetState(0, acquires)) {
6 setExclusiveOwnerThread(current);
7 return true;
8 }
9 }
10 else if (current == getExclusiveOwnerThread()) {
11 int nextc = c + acquires;
12 if (nextc < 0) // overflow
13 throw new Error("Maximum lock count exceeded");
14 setState(nextc);
15 return true;
16 }
17 return false;
18 }
此段代码不做分析;网上查了一下非公平锁与公平锁的区别,引用如下
"
在公平的锁上,线程按照他们发出请求的顺序获取锁,但在非公平锁上,则允许‘插队’:当一个线程请求非公平锁时,如果在发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁。 非公平的ReentrantLock 并不提倡 插队行为,但是无法防止某个线程在合适的时候进行插队。
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。
非公平锁性能高于公平锁性能的原因:
在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。
假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。
当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。
"可以理解为非公平锁的性能更高一些;
也就是说ConcurrenthashMap是利用可重入锁里面的非公平锁来给segment加上锁的;
针对concurrentHashmap的数据结构,我查阅相关资料,如下:
如上所示:concurrentHashmap锁住的颗粒度更小,针对segment进行锁;
对concurrentHashMap的分析到此为止,确有许多代码细节,我还没有看明白,后面搞清楚了再研究欢迎扔砖。