ConcurrentHashmap理解

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








猜你喜欢

转载自blog.csdn.net/hackland2012/article/details/61912430