【并发容器】2 ConnrentHashMap

1 并发容器的演进

工具类转换线程安全
Collections.synchronizedList(list) 内部采用同步代码块的形式,加锁保证线程安全。

public E get(int index) {    synchronized (mutex) {return list.get(index);}}

Vectior和HashTable则采用的是同步方法加锁。

绝大多数情况下,ConcurrentHashMap和CopyOnWriteArrayList性能更好。CopyOnWriteArrayList适合读多写少,list一直修改的话,可以采用Collections形式。

  1. ConcurrentHashMap:线程安全的HashMap
  2. CopyOnWriteArrayList:线程安全的List
  3. BlockingQueue:接口,阻塞队列,非常适用于作为数据共享的通道
  4. ConCurrentLinkedQueue:高效的非阻塞并发队列,使用链表实现。可以视作一个线程安全的LinkedList

2 ConnrentHashMap

2.1 HashMap

为什么HashMap线程不安全?

  1. 线程同时put操作碰撞导致数据丢失;
  2. 线程同时put扩容时导致数据丢失。
  3. 死循环造成CPU 100%。因为在多线程并发扩容时,在resize的transer方法可能会产生环形链表,从而导致死循环。https://coolshell.cn/articles/9606.html

2.2 ConcurrentHashMap的实现原理

1.7版本结构,最外层设置多个segment,每个segment底层结构与HashMap类似,仍然是数组和链表组成拉链法。每个segment有独立的ReentrantLock锁,相互之间不影响,提高并发效率。默认是16个Segment,也就是说支持16个线程的并发写。segment初始化时设置,设置后不能修改。

在这里插入图片描述
在这里插入图片描述

1.8版本,底层采用node+CAS+synchronized实现。结构与JDK1.8版本的HashMap类似,相对于1.7版本,锁粒度更小,每个node都有并发能力。

源码分析主要参考put/get方法实现。

final V putVal(K key, V value, boolean onlyIfAbsent) {
        // key和value不允许为空
        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;
            // 数组为空,初始化数组
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            // 数组索引位置桶为空,CAS初始化Node
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                    break;                   // no lock when adding to empty bin
            }
            // 数组正在resize扩容,则帮助扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            // 找到值并且相同,则直接返回
            else if (onlyIfAbsent // check first node without acquiring lock
                     && fh == hash
                     && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                     && (fv = f.val) != null)
                return fv;
            else { // 采用synchronized内置锁写入数据
                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);
                                    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;
                            }
                        }
                        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;
    }

区别:

  1. 数据结构:主要体现在并发度
  2. Hash碰撞:1.7采用拉链法,1.8采用了HashMap的红黑树转换的形式。
  3. 并发工具:1.7采用ReentrantLock,1.8采用synchronized和CAS。

2.3 使用时需要注意的问题

使用ConcurrentHashMap只能保证对于map的put、get操作是线程安全的,而组合操作不能保证线程安全,例如a++。

public class OptionsNotSafe implements Runnable {
    /**
     * map容器
     */
    private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

    public static void main(String[] args) throws InterruptedException {
        map.put("sum", 0);
        Thread thread1 = new Thread(new OptionsNotSafe());
        Thread thread2 = new Thread(new OptionsNotSafe());
        thread1.start();
        thread2.start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            Integer old = map.get("sum");
            map.put("sum", old + 1);
        }
    }
}

可以通过synchronized同步代码块解决,但是会破坏ConcurrentHashMap思想。

    public void run() {
        for (int i = 0; i < 10000; i++) {
            while (true){
                Integer old = map.get("sum");
                Integer newVal = old + 1;
                if (map.replace("sum", old, newVal)) {
                    break;
                }
            }
        }
    }

replace采用的是自旋的思想,更新成功才返回true。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值