求助,关于ConcurrentHashMap的锁

寂寞灵魂 2017-03-13 02:36:49
我看网上有说ConcurrentHashMap是有分段锁的,看ConcurrentHashMap的源码里好像确实有,但是又看到thinking in java上说ConcurrentHashMap是免锁的,免锁的原理是修改的时候是在容器数据结构的某个部分(有时是整体)的副本上进行修改,并且这个副本在修改过程中是不可视的,修改完成后,被修改的结构会自动与主数据结构进行交换。
求大神解读啊...
...全文
222 3 打赏 收藏 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
寂寞灵魂 2017-03-13
  • 打赏
  • 举报
回复
引用 2 楼 zs808 的回复:
以下代码摘自JDK1.7的ConcurrentHashMap$Segment源码:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }
第一点,Segment是ConcurrentHashMap的“分段锁”的实现,每一个Segment都会有一个Lock。可以看到有调用tryLock与unlock方法这也就是“ConcurrentHashMap是有分段锁的论证”。 但是请注意,这里调用锁的方法的命名,是tryLock而不是lock。tryLock调用的是Sync里的tryAcquire方法,这个方法会进行一次cas锁的抢占操作,cas操作是“无锁同步”的核心。在tryLock失败时,会调用scanAndLockForPut方法,下面我们来看看scanAndLockForPut这个方法的实现:
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            int retries = -1; // negative while locating node
            while (!tryLock()) {
                HashEntry<K,V> f; // to recheck first below
                if (retries < 0) {
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }
这里维护一个while(!tryLock()),当且仅当retries > MAX_SCAN_RETRIES时才会触发真正的锁定(调用lock方法)。这是“轻量锁转重量锁”的实现。 ================ 第二点,关于“免锁”,免锁这个词针对的是ConcurrentHashMap的get方法,同样,我们来看看ConcurrentHashMap的get()方法:
public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key.hashCode());
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }
可以看到,在get方法中,做的操作只有两个,第一是获取Segment,第二是获取Segment的HashEntry值,然而,这两个操作都是通过UNSAFE.getObjectVolatile来实现的,UNSAFE.getObjectVolatile这个方法是对象数据获取的原子方法,在应用程序级别没有“锁”,所以,ConcurrentHashMap是“免锁的”。 ================== 第三点,关于“副本”,Segment里面维护这一系列的HashEntry,每一个hashEntry本质上就是一个单向链表的头部,也就是说,每一个hashEntry起始都是当前HashEntry加上当前HashEntry的后续Entry的“副本”。这样,对于ConcurrentHashMap来说,每一次获取一个HashEntry就是获取一个“副本”,对这个Entry的操作也就是对“副本的操作”,由于这个副本与原链表是父子关系,所以对这个Entry的更改会自动更新到原链表上。这也就是Bruce Eckel大牛的意思。 第一次码这么多字,如有疏漏,请指出,欢迎一起讨论~
多谢回复。
zs808 2017-03-13
  • 打赏
  • 举报
回复
以下代码摘自JDK1.7的ConcurrentHashMap$Segment源码:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }
第一点,Segment是ConcurrentHashMap的“分段锁”的实现,每一个Segment都会有一个Lock。可以看到有调用tryLock与unlock方法这也就是“ConcurrentHashMap是有分段锁的论证”。 但是请注意,这里调用锁的方法的命名,是tryLock而不是lock。tryLock调用的是Sync里的tryAcquire方法,这个方法会进行一次cas锁的抢占操作,cas操作是“无锁同步”的核心。在tryLock失败时,会调用scanAndLockForPut方法,下面我们来看看scanAndLockForPut这个方法的实现:
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            int retries = -1; // negative while locating node
            while (!tryLock()) {
                HashEntry<K,V> f; // to recheck first below
                if (retries < 0) {
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }
这里维护一个while(!tryLock()),当且仅当retries > MAX_SCAN_RETRIES时才会触发真正的锁定(调用lock方法)。这是“轻量锁转重量锁”的实现。 ================ 第二点,关于“免锁”,免锁这个词针对的是ConcurrentHashMap的get方法,同样,我们来看看ConcurrentHashMap的get()方法:
public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        int h = hash(key.hashCode());
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }
可以看到,在get方法中,做的操作只有两个,第一是获取Segment,第二是获取Segment的HashEntry值,然而,这两个操作都是通过UNSAFE.getObjectVolatile来实现的,UNSAFE.getObjectVolatile这个方法是对象数据获取的原子方法,在应用程序级别没有“锁”,所以,ConcurrentHashMap是“免锁的”。 ================== 第三点,关于“副本”,Segment里面维护这一系列的HashEntry,每一个hashEntry本质上就是一个单向链表的头部,也就是说,每一个hashEntry起始都是当前HashEntry加上当前HashEntry的后续Entry的“副本”。这样,对于ConcurrentHashMap来说,每一次获取一个HashEntry就是获取一个“副本”,对这个Entry的操作也就是对“副本的操作”,由于这个副本与原链表是父子关系,所以对这个Entry的更改会自动更新到原链表上。这也就是Bruce Eckel大牛的意思。 第一次码这么多字,如有疏漏,请指出,欢迎一起讨论~
寂寞灵魂 2017-03-13
  • 打赏
  • 举报
回复
人工置顶啊啊啊啊啊啊

62,620

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧