MAP安全类:ConcurrentHashMap解析

下面先来说说map类下的 HashMap:

HashMap的构造方法,可以写初始化容量和加载因子,默认是16和0.75,这里可以从源码查看:

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final int MAXIMUM_CAPACITY = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

但,HashMap在多线程下并不安全,代码如下:

package map;

import java.util.HashMap;
import java.util.UUID;

public class ConcurrentHashMapTest {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        //等价于 HashMap<String, String> map1 = new HashMap<>(16, 0.75f);
        //0.75f是加载因子,16是初始化容量,最大容量是 1 << 30,即2^30
        for (int i = 1; i <=100 ; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map.toString());
            }).start();
        }
    }
}

这时候会出现ConcurrentModificationException错误:

Exception in thread "Thread-68" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
	at java.util.AbstractMap.toString(AbstractMap.java:554)
	at map.ConcurrentHashMapTest.lambda$main$0(ConcurrentHashMapTest.java:14)
	at java.lang.Thread.run(Thread.java:748)

这是因为hashmap在扩容加入元素期间可能被其他线程抢先遍历,则会出现这个问题:解决这个问题的方法可以使用hashtable或者用ConcurrentHashMap。

1、hashtable的解决办法是,每个方法声明关键字上加上synchronized,有点蠢,这样粒度大,花销很大。

   public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

2、我们这里使用ConcurrentHashMap

package map;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapTest {
    public static void main(String[] args) {
//        HashMap<String, String> map = new HashMap<>();
//        Hashtable<Object, Object> map = new Hashtable<>(); //每个方法都加锁粒度太大
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        //等价于 HashMap<String, String> map1 = new HashMap<>(16, 0.75f);
        //0.75f是加载因子,16是初始化容量,最大容量是 1 << 30,即2^30
        for (int i = 1; i <=100 ; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map.toString());
            }).start();
        }
    }
}

执行后发现,问题解决:执行成功结果

{Thread-51=8df81, Thread-62=8ef89, Thread-50=97dcb, Thread-61=0f49c, Thread-60=6926c, Thread-55=26691, Thread-54=f0067, Thread-53=0e06f, Thread-52=182b6, Thread-48=15355, Thread-59=98a6e, Thread-58=e4571, Thread-46=3fff8, Thread-57=6675e, Thread-45=3e198, Thread-56=efe09, Thread-49=89a27}
{Thread-51=8df81, Thread-50=97dcb, Thread-71=6ad20, Thread-70=128ba, Thread-66=75374, Thread-65=5ff5c, Thread-64=e3fc4, Thread-63=7dcab, Thread-48=15355, Thread-69=a0952, Thread-46=3fff8, Thread-68=59d2e, Thread-45=3e198, Thread-67=934d8, Thread-49=89a27, Thread-62=8ef89, Thread-61=0f49c, Thread-60=6926c, Thread-55=26691, Thread-54=f0067, Thread-53=0e06f, Thread-52=182b6, Thread-59=98a6e, Thread-58=e4571, Thread-57=6675e, Thread-56=efe09}

ConCurrentHashMap的底层是:散列表+红黑树,与HashMap是一样的。
ConcurrentHashMap通过在部分加锁和利用CAS算法来实现同步。
这是put方法的源码:

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            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
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            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;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

下面是我粗略的分析
在这里插入图片描述
这是对节点的处理:

 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;
                            }
                        }
                    }
                }

链表如果大于8,则转换成树:

 if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值