ConcurrentHashMap有感

302 阅读2分钟

都说HashMap是线程不安全的,ConcurrentHashMap是线程安全的。这次来看看ConcurrentHashMap是如何保证线程安全的?

先来看写操作 put()方法

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }

tabAt(tab, i = (n - 1) & hash))是定位到数组的index位置,如果此时该index位置上是空的,就CAS插入一个结点。 可见这里是用CAS来保证原子性的

如果此时CAS操作失败呢,就进入下一次循环,走到下面这个代码块里

else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }

这里大家可以看到synchronized (f),这个f是数组index位置上的结点,已经很明显,这就是分段锁的内幕。写操作不需要对整个数组进行加锁,只要对定位到的数组index结点加锁就可以了,这样就提高了并发效率。该数组位对应的链表或者红黑树只允许一个线程操作了。 可见,ConcurrentHashMap保证并发写是用CAS+synchronized 分段锁思想来实现的

那下面来看看读操作:get()

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

读操作这边只是用volatile的可见性,并没有保证原子性。只保证当前读到的是最新的数据,可能刚读好,value就被其他线程修改l。