ConcurrentHashmap理解

    xiaoxiao2021-03-25  61

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

    转载请注明原文地址: https://ju.6miu.com/read-38514.html

    最新回复(0)