首页 > 其他分享 >理解ConcurrentHashMap的多线程执行

理解ConcurrentHashMap的多线程执行

时间:2023-05-25 23:11:06浏览次数:40  
标签:map ConcurrentHashMap System println 理解 sleep put 多线程 size

理解ConcurrentHashMap的多线程执行

多线程下ConcurrentMap单个操作的顺序性/原子性

结论:ConcurrentHashMap单个操作,例如 get/put/remove都有原子性,即使操作同一个key,在底层会通过synchronized锁去排队执行。所以多线程下,任意的执行结果都是合理的。

lab1:三个线程,操作同一个ConcurrentHashMap,且get/put/remove同一个key。

public class ExpWithConcurrent {
    public static void main(String[] args) {
        // 3 threads, operate with ConcurrentHashMap
        ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();

        Thread t1 = new Thread(() -> {
            // put
            randomSleep();
            String ans = map.put(1, "one");
            System.out.println("T1 put: " + ans);
        }, "T1");
        Thread t2 = new Thread(() -> {
            // remove
            randomSleep();
            String ans = map.remove(1);
            System.out.println("T2 remove: " + ans);
        }, "T2");
        Thread t3 = new Thread(() -> {
            // get
            randomSleep();
            System.out.println("T3 get: " + map.get(1));
        }, "T3");

        t1.start();
        t2.start();
        t3.start();
    }

    static void randomSleep() {
        int time = new Random().nextInt(1000);
        try {
            System.out.println(Thread.currentThread().getName() + " will sleep " + time + " ms");
            TimeUnit.MILLISECONDS.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

输出结果如下:

T1 will sleep 956 ms
T2 will sleep 367 ms
T3 will sleep 222 ms
T3 get: null
T2 remove: null
T1 put: null

T2 will sleep 286 ms
T1 will sleep 415 ms
T3 will sleep 795 ms
T2 remove: null
T1 put: null
T3 get: one

T1 will sleep 534 ms
T2 will sleep 131 ms
T3 will sleep 217 ms
T2 remove: null
T3 get: null
T1 put: null

T3 will sleep 43 ms
T2 will sleep 719 ms
T1 will sleep 136 ms
T3 get: null
T1 put: null
T2 remove: one

….

通过分析可以知道,三个线程执行的顺序,总共有6种。在并发的情况下都有可能发生。网上有人建议测试并发程序,可以使用随机sleep,让线程乱序,观察执行的结果。对于这个lab来说,怎么操作都是正确的,没有一个严格的限制。

应用程序要维护一致性

lab2:如果要维护一个大小始终是3个元素的map(例如LRU cache),然后,多个线程执行put,如果当前等于3,就随机删除一个,然后再put。这个检查大小size、remove、put不是一个原子操作,应该会有问题(map的size可能超过3),代码如下:

public class Exp2WithConcurrent {
    public static void main(String[] args) {
        // 3 threads, operate with ConcurrentHashMap
        // initially we have 3 items in map.
        ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");

        // we must maintain the size, but also put new item concurrently
        Runnable task = () -> {
            randomSleep();

            String name = Thread.currentThread().getName();
            System.out.println(name + "---size:" + map.size());

            if (map.size() > 3) {
                System.out.println("yeah! inconsistent found!!!!");
            }

            if (map.size() >= 3) {
                map.remove(map.keys().nextElement());
            }
            int id = new Random().nextInt(10000);
            String ans = map.put(id, Thread.currentThread().getName());
            System.out.println(Thread.currentThread().getName() + " put: " + ans);
            System.out.println(name + "---size(after):" + map.size());
            if (map.size() > 3) {
                System.out.println("yeah! inconsistent found!!!!");
            }
        };

        for (int i = 0; i < 200; i++) {
            new Thread(task, "T" + i).start();
        }
    }

    static void randomSleep() {
        int time = new Random().nextInt(1000);
        try {
            System.out.println(Thread.currentThread().getName() + " will sleep " + time + " ms");
            TimeUnit.MILLISECONDS.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

结果:当线程数比较多时,map的size,会比较大。

T44 will sleep 956 ms
T45 will sleep 893 ms
T1---size:3
T48 will sleep 122 ms
T31 will sleep 130 ms
T1 put: null
T1---size(after):3

…….

T7---size(after):4
yeah! inconsistent found!!!!
T165---size:4
yeah! inconsistent found!!!!
T165 put: null
T165---size(after):4
yeah! inconsistent found!!!!
T179---size:4
yeah! inconsistent found!!!!
T179 put: null
T179---size(after):4
yeah! inconsistent found!!!!
T110---size:4
yeah! inconsistent found!!!!
T110 put: null
T110---size(after):4
yeah! inconsistent found!!!!

那么针对lab2,如何保证应用程序的一致性呢?让size始终是3呢? 这个需要保证原子性了,通过加锁。

核心代码:
     Runnable task = () -> {
            randomSleep();

            synchronized (map) { // 加锁,让多线程去排队执行,保证 size(), remove(), put()的原子性
                String name = Thread.currentThread().getName();
                System.out.println(name + "---size:" + map.size());

                if (map.size() > 3) {
                    System.out.println("yeah! inconsistent found!!!!");
                }

                if (map.size() >= 3) {
                    map.remove(map.keys().nextElement());
                }
                int id = new Random().nextInt(10000);
                String ans = map.put(id, Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + " put: " + ans);
                System.out.println(name + "---size(after):" + map.size());
                if (map.size() > 3) {
                    System.out.println("yeah! inconsistent found!!!!");
                }
            }
        };

思考:

  • 既然都加锁了,还有必要用ConcurrentHashMap吗?直接使用普通的Map?
    答案:可以的!因为sync块里面,始终只有一个线程执行。

  • 或者不加锁,使用同步类的Hashtable,或者是Collections.synchronizedMap(map)也可以?
    答案:不可以,这就跟使用ConcurrentHashMap不加锁,是一样的,只能保证Map的单个操作的原子性,不能保证多个组合操作的原子性。

public class Exp2WithConcurrent { // 使用普通Map + synchronized 可以。正确!
    public static void main(String[] args) {
        // 3 threads, operate with ConcurrentHashMap
        // initially we have 3 items in map.
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "one");
        map.put(2, "two");
        map.put(3, "three");

        // we must maintain the size, but also put new item concurrently
        Runnable task = () -> {
            randomSleep();

            synchronized (map) {
                String name = Thread.currentThread().getName();
                System.out.println(name + "---size:" + map.size());

                if (map.size() > 3) {
                    System.out.println("yeah! inconsistent found!!!!");
                }

                if (map.size() >= 3) {
                    map.remove(map.keySet().iterator().next());
                }
                int id = new Random().nextInt(10000);
                String ans = map.put(id, Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName() + " put: " + ans);
                System.out.println(name + "---size(after):" + map.size());
                if (map.size() > 3) {
                    System.out.println("yeah! inconsistent found!!!!");
                }
            }
        };

        for (int i = 0; i < 200; i++) {
            new Thread(task, "T" + i).start();
        }
    }

    static void randomSleep() { ... }
}

标签:map,ConcurrentHashMap,System,println,理解,sleep,put,多线程,size
From: https://www.cnblogs.com/xianzhon/p/17433270.html

相关文章

  • 关于虚幻多线程的学习
    1先去复习了下C++的多线程异步和单线程异步2看了下虚幻中,用Tick模拟局部异步,算是单线程异步3根据官方文档,继承FRunnable类来进行虚幻中的多线程使用在涉及到使用共享资源及线程同步的时候,用到了FScopeLock锁,FScopeLock(&CriticalSection).  里面的是FCriticalSect......
  • 关于M2 的一些理解。
    硬件接口的类型。     ......
  • 我对TCP三次握手的理解
    客户端:买菜大妈服务端:菜贩两次握手买菜大妈:这萝卜2毛一斤卖不?菜贩:卖。你要几斤?如果这两个人是在app上买的。互相之间看不到,那么大妈不想卖,走了。菜贩还在等她回话,这就浪费时间了。三次握手买菜大妈:这萝卜2毛一斤卖不?菜贩:卖。你要几斤?买菜大妈:我不买,我就问问。买卖不成情谊在,都不......
  • Java的CompletableFuture,Java的多线程开发
    三、Java8的CompletableFuture,Java的多线程开发1、CompletableFuture的常用方法以后用到再加runAsync():开启异步(创建线程执行任务),无返回值supplyAsync():开启异步(创建线程执行任务),有返回值thenApply():然后应用,适用于有返回值的结果,拿着返回值再去处理。exceptionally......
  • 深入理解数据库中的表、用户、表空间和模式的关系
    在数据库管理系统中,如Oracle,对关键概念,包括表、用户、表空间和模式之间的关系应有较深理解。这些概念对于正确管理和保护数据库中的数据至关重要。在本文中,我们将重点整理和澄清这些概念,并解释它们之间的关系。一明确每个概念的含义。表:表是数据库中存储数据的基本结构。它由列和......
  • 多线程合集(三)---异步的那些事之自定义AsyncTaskMethodBuilder
    引言之前在上一篇文章中多线程合集(二)---异步的那些事,async和await原理抛析,我们从源码去分析了async和await如何运行,以及将编译后的IL代码写成了c#代码,以及实现自定义的Awaiter,自定义异步状态机同时将本系列的第一篇文章的自定义TaskScheduler和自定义的Awaiter......
  • 关于对Promise 以及async的理解!
    为了解决Promise.then和.catch看起来比较乱以及写起来比较麻烦的问题,可以用async配合await来调用Promise实现异步操作。代码的写法和同步有点类似。例如:asyncfunctionget(url){try{letresp=awaitfecth(url);returnresp.json();}catch(e){//出错了}}用a......
  • Java 泛型:理解和应用
    概述泛型是一种将类型参数化的动态机制,使用得到的话,可以从以下的方面提升的你的程序:安全性:使用泛型可以使代码更加安全可靠,因为泛型提供了编译时的类型检查,使得编译器能够在编译阶段捕捉到类型错误。通过在编译时检查类型一致性,可以避免在运行时出现类型转换错误和ClassCastE......
  • 理解数据报文在网络设备中的传输细节
    数据报文在网络设备中是如何传输的呢?且听我娓娓道来。这么说吧,本文讲述的是数据报文在普通的二层或三层设备即交换机或路由器之间的传输过程,不涉及防火墙一类网络设备间的传输场合。请看下图,这是一个简易的网络结构,由一台路由器两台交换机组成。假设各设备的MAC地址、IP地址为图......
  • Java笔记(七):多线程
    Java默认有2个线程:main+GC并发:CPU单核,交替执行并行:CPU多核,多个线程可以同时执行(提高使用效率:线程池)Runtime.getRuntime().availableProcessors()//当前CPU可用核数多线程实现方式继承Thread类,重写run方法这样代码的写法简单,符合大家的习惯,但是直接继承Thread类有一......