• java的ConCurrentHashMap


    一般的应用的编程,用到ConCurrentHashMap的机会很少,就象大家调侃的一样:只有面试的时候才用得着。

    但还是有。

    网上关于这个的资料,多如牛毛,大部分是原理分析和简单例子。

    原理的核心就一个:并发Map其实是多个HashTable拼凑的,可以在写的时候具有更小的锁粒度,它适用于读多写少的场景。其它细枝末节有空再关注了。知道这个就足够了。

    关于的原理等,可以看看 ConcurrentHashMap原理分析(一)-综述 - 猿起缘灭 - 博客园 (cnblogs.com)

    不过许多文章并没有讨论的使用的注意事项:如何在并发的情况下,正确修改某个key。

    我们举一个最简单的例子,一个map有两个key,分别是a和b,有10来个线程分别修改a,b。线程的作用就是把a,b的值取出+1。

    最后要求,运行一段时间后,a,b的值应该是它们分别被操作的次数。

    在开始前,重申下:并发map只有部分操作是上锁的,并非说所有的操作都会上锁;特别map有上锁操作,并不意味着其它关联代码都上锁。

    如果要了解哪些是上锁的,请查看源码,现在eclipse查看源码简直不要太方便。

    来个例子:

    运行环境:windows11,jdk17

    /**
     * 
     */
    package study.base.types.map;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author luzhifei
     *
     */
    public class ConcurrentMapRunable implements Runnable {
    
        private AtomicInteger loss;
    
        private ConcurrentHashMap<String, Integer> flag;
    
        public ConcurrentMapRunable(ConcurrentHashMap<String, Integer> flag, AtomicInteger loss) {
            this.flag = flag;
            this.loss = loss;
        }
    
        @Override
        public void run() {
            String tname = Thread.currentThread().getName();
            synchronized (flag) {
                Integer oldVal = flag.get("score").intValue();
                Integer newVal = oldVal + 1;
                System.out.println(tname + " :" + newVal.toString());
                flag.replace("score", oldVal, newVal);
                loss.incrementAndGet();
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            int tty = 33;
    
            ConcurrentHashMap<String, Integer> score = new ConcurrentHashMap<String, Integer>(1, 1);
            score.put("score", 0);
    
            AtomicInteger loss = new AtomicInteger(0);
            ConcurrentMapRunable job = new ConcurrentMapRunable(score, loss);
    
            // 初始化线程,并运行
            List<Thread> jobs = new ArrayList<>();
            for (int i = 0; i < tty; i++) {
                Thread t = new Thread(job, i + "");
                jobs.add(t);
            }
    
            for (int i = 0; i < tty; i++) {
                jobs.get(i).start();
            }
    
            // 等待返回
            while (loss.intValue() < tty) {
    
            }
    
            System.out.println("total score is:" + score.get("score"));
        }
    
    }

    上文中红色部分是自增的逻辑:取出并加一,然后放回去。结果是对的,因为synchronized了。如果没有加synchronized,那么结果就不是预期的。

    但这样写,好像没有必要使用并发map,那么要怎么写了?

       原代码:
       synchronized (flag) {
                Integer oldVal = flag.get("score").intValue();
                Integer newVal = oldVal + 1;
                System.out.println(tname + " :" + newVal.toString());
                flag.replace("score", oldVal, newVal);
                loss.incrementAndGet();
            }
        修改后代码:
    
        flag.replace("score", flag.get("score") + 1);

    注:以上代码,只适用于当前这种简单场景,所以可以不需要synchronized,因为replace代码自带。

    为什么一行就可以了?我们看下并发map的replace代码:

    public V replace(K key, V value) {
            if (key == null || value == null)
                throw new NullPointerException();
            return replaceNode(key, value, null);
        }
    
    final V replaceNode(Object key, V value, Object cv) {
            int hash = spread(key.hashCode());
            for (Node<K,V>[] tab = table;;) {
                Node<K,V> f; int n, i, fh;
                if (tab == null || (n = tab.length) == 0 ||
                    (f = tabAt(tab, i = (n - 1) & hash)) == null)
                    break;
                else if ((fh = f.hash) == MOVED)
                    tab = helpTransfer(tab, f);
                else {
                    V oldVal = null;
                    boolean validated = false;
                    synchronized (f) {
                        if (tabAt(tab, i) == f) {
                            if (fh >= 0) {
                                validated = true;
                                for (Node<K,V> e = f, pred = null;;) {
                                    K ek;
                                    if (e.hash == hash &&
                                        ((ek = e.key) == key ||
                                         (ek != null && key.equals(ek)))) {
                                        V ev = e.val;
                                        if (cv == null || cv == ev ||
                                            (ev != null && cv.equals(ev))) {
                                            oldVal = ev;
                                            if (value != null)
                                                e.val = value;
                                            else if (pred != null)
                                                pred.next = e.next;
                                            else
                                                setTabAt(tab, i, e.next);
                                        }
                                        break;
                                    }
                                    pred = e;
                                    if ((e = e.next) == null)
                                        break;
                                }
                            }
                            else if (f instanceof TreeBin) {
                                validated = true;
                                TreeBin<K,V> t = (TreeBin<K,V>)f;
                                TreeNode<K,V> r, p;
                                if ((r = t.root) != null &&
                                    (p = r.findTreeNode(hash, key, null)) != null) {
                                    V pv = p.val;
                                    if (cv == null || cv == pv ||
                                        (pv != null && cv.equals(pv))) {
                                        oldVal = pv;
                                        if (value != null)
                                            p.val = value;
                                        else if (t.removeTreeNode(p))
                                            setTabAt(tab, i, untreeify(t.first));
                                    }
                                }
                            }
                            else if (f instanceof ReservationNode)
                                throw new IllegalStateException("Recursive update");
                        }
                    }
                    if (validated) {
                        if (oldVal != null) {
                            if (value == null)
                                addCount(-1L, -1);
                            return oldVal;
                        }
                        break;
                    }
                }
            }
            return null;
        }

    不关心的代码一堆,有用的就是"synchronized (f) {",也就是说并发map的有些操作自带了同步锁。

    所以,学习并发的时候,如果仅仅只是为了满足低下并发要求,那么不需要了解那么多,关键了解几点即可:

    • 计算机分时原理,计算机多核并行原理
    • 并发概念
    • 并发的软实现
    • java如何创建线程
    • 如何选择适当的锁粒度
    • java有哪些线程安全的数据类型
    • java 的synchronize用法
    • 尽量测试

     阅读每个类型的源码,并不是必要的,只是在有空或者必要的时候才做。

  • 相关阅读:
    5.2 i++
    5.1 赋值语句
    Implement Queue using Stacks
    Binary Tree Paths
    Single Number III
    Ugly Number
    SurfaceHolder.Callback
    intellj(idea) 编译项目时在warnings 页签框里 报 “xxx包不存在” 或 “找不到符号” 或 “未结束的字符串字面值” 或 “需要)” 或 “需要;”等错误提示
    Hibernate添加日志--log4j
    java实现网页验证码
  • 原文地址:https://www.cnblogs.com/lzfhope/p/16332665.html
Copyright © 2020-2023  润新知