您的位置 首页 java

ConcurrentHashMap源码分析 – put方法

1.案例

多线程 中使用 HashMap 时,会有 线程安全 的问题,下面的案例中使用1000个 线程 向同一个HashMap中分别插入10条数据时,就会出现线程安全问题

 package org.poiuy.java.juc.concurrenthashmap;

import java.util. hash Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * ConcurrentHash put方法
 *
 * HashMap put方法分析,线程安全问题
 * ConcurrentHashMap put方法分析
 */public class ConcurrentHashMapPutApplication {
    private static final int THREAD_NUM = 1000;

    public static void main(String[] args) {
        hashmapMultiPut();

        concurrentHashmapMultiPut();
    }

    /**
     * 使用ConcurrentHashMap代替HashMap
     */    private static void concurrentHashmapMultiPut() {
        try {
            //使用ConcurrentHashMap代替HashMap
            ConcurrentHashMap map = new ConcurrentHashMap();
            CountDownLatch latch = new CountDownLatch(THREAD_NUM);

            //模拟10000个线程向ConcurrentHashMap中同时添加10个数据
            for(int i = 0;i < THREAD_NUM;i++){
               new Thread(() -> {
                    for(int j = 0;j < 10;j++){
                        map.put(Thread.currentThread().getId() + "." + j,Thread.currentThread().getId() + " : " + j);
                    }
                   latch.countDown();
                }).start();
            }

            latch.await();
            System.out.println("ConcurrentHashMap count :" + map.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * HashMap的put多线程问题
     */    private static void hashmapMultiPut() {
        try {
            //HashMap的put多线程问题
            HashMap map = new HashMap();
            CountDownLatch latch = new CountDownLatch(THREAD_NUM);

            //模拟10000个线程向HashMap中同时添加10个数据
            for(int i = 0;i < THREAD_NUM;i++){
                new Thread(() -> {
                    for(int j = 0;j < 10;j++){
                        map.put(Thread.currentThread().getId() + "." + j,Thread.currentThread().getId() + " : " + j);
                    }
                    latch.countDown();
                }).start();
            }

            latch.await();
            System.out.println("HashMap count :" + map.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}  

从输出的结果我们可以看到HashMap最后的总数总是小于10000,当我们使用ConcurrentHashMap时就不会出现问题

ConcurrentHashMap源码分析 - put方法

2.HashMap的线程安全问题

HashMap的put方法中,如果在多线程环境下,很对地方都会有线程安全的问题

比如631行,判断数组中没有元素时,直接创建新的节点放入到数组下标中

可以看出以上以及更多的地方都不是原子操作,将会带来安全性问题

为了解决安全性问题,我们可以使用加锁的方式,比如使用Hashtable来替代HashMap,但是这个方式性能不是很高

多线程环境下推荐使用ConcurrentHashMap

ConcurrentHashMap源码分析 - put方法

3.ConcurrentHashMap的put方法

ConcurrentHashMap的put方法中为了解决线程安全问题,使用了CAS和锁的方式来处理

CAS是一种乐观锁的实现,它在操作是将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值,整个过程是原子操作

另外加锁操作时,也只是对数组中要操作的单个下标进行加锁,不影响其他下标下的元素的操作

     final V putVal(K key, V value, boolean onlyIfAbsent) {
//计算hash值
        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; K fk; V fv;

//集合为空,进行初始化操作
//此处会有安全问题,在initTable方法中处理
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();

//如果插入的数据在数组指定下标下的节点为空,则直接将新的数据添加到数组下标下
//此处判断是使用Unsafe的getReferenceAcquire方法进行判断,该方法是原子性的
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//使用Unsafe的compareAndSetReference方法设置值,方法是原子性的
//如果数组下标下为空,则创建新节点并添加到数组中
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                     break ;
            }

//如果该节点正在执行扩容移动操作,则调用helpTransfer方法帮助扩容移动
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);

//key值时数组下标中对应的元素,则进行值修改操作
//如果onlyIfAbsent参数为true,,数组下标中的元素的key值存在且value不为空,则不进行修改动作,直接退出
            else if (onlyIfAbsent && fh == hash && ((fk = f.key) == key || (fk != null && key.equals(fk))) && (fv = f.val) != null)
                return fv;

//其他情况
//1.节点是数组下标中的元素,且可以进行修改
//2.节点不存在,且对应的数组下标已经存在元素
            else {
                V oldVal = null;

//对数组下标中元素f加锁,后续操作是线程安全的
                synchronized (f) {
//进一步判断,防止其他线程修改或删除f节点
                    if (tabAt(tab, i) == f) {
// 链表 结构处理
                        if (fh >= 0) {
                            binCount = 1;
//遍历链表
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
//修改value逻辑
                                if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
//添加luoji,添加到链表尾部
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key, value);
                                    break;
                                }
                            }
                        }

// 红黑树 处理,调用putTreeVal方法
                        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;
                            }
                        }

//ReservationNode抛出异常
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }

//是否要转变成为红黑树
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }

//扩容方法
        addCount(1L, binCount);
        return null;
    }  

文章来源:智云一二三科技

文章标题:ConcurrentHashMap源码分析 – put方法

文章地址:https://www.zhihuclub.com/201293.shtml

关于作者: 智云科技

热门文章

网站地图