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的分析到此为止,确有许多代码细节,我还没有看明白,后面搞清楚了再研究欢迎扔砖。