Java ConcurrentHashMap 怎么用
平时写Java代码时,大家总会碰到“多线程同时抢数据”的场景——比如电商秒杀时,几百个用户同时刷新商品库存,或者外卖系统里多个骑手同时更新订单状态,这时候要是用普通HashMap,大概率会出数据错乱的问题,这时候ConcurrentHashMap就是专门解决这类问题的工具。
为什么不用普通HashMap?
先给大家举个生活化的例子:把HashMap当成只有1个收银台的小卖部,平时只有1个顾客结账没问题,但如果突然来100个顾客同时排队,大家挤在同一个收银台前面,你推我搡不说,还可能有人抢了别人的商品,或者把钱塞错了位置。Java里的普通HashMap就像这个只有1个收银台的小卖部,多线程同时操作时,会出现“数据被覆盖”“读不到正确值”的bug,甚至可能导致程序崩溃。

ConcurrentHashMap的核心用法
它的设计就像把小卖部改成了“多个分区收银台”,每个分区对应一段数据,多线程操作时只需要抢对应分区的锁,不会影响其他分区,效率高还安全。核心用法主要分这几类,都是日常开发里最常用的:
初始化:直接用,不用额外配置
就像你开小卖部分区,不用特意去跟装修师傅说怎么分,ConcurrentHashMap的构造方法已经帮你做好了基础配置,直接new就可以用,比如:
`java
// 创建一个存用户信息的ConcurrentHashMap,键是用户ID(字符串),值是用户姓名(字符串)
ConcurrentHashMap<String, String> userMap = new ConcurrentHashMap<>();
`
不用像早期的线程安全类那样,还要套一层Collections.synchronizedMap(),省了很多麻烦。
存数据:用put,别用put(旧版)
存数据的方法是put(key, value),比如要把用户ID“001”对应姓名“张三”存进去:
`java
userMap.put("001", "张三");
`
这里要注意,别用普通HashMap里的putIfAbsent(虽然它也有,但ConcurrentHashMap的这个方法是线程安全的)——比如你想“只有当这个用户ID还没存的时候,才存进去”,如果用普通HashMap,多线程下可能会有多个线程同时判断“没存”然后都存进去,导致数据重复;但用ConcurrentHashMap的putIfAbsent,多个线程操作同一个key时,只会有一个线程成功,完美解决这个问题,比如:
`java
// 只有key为"001"的数据不存在时,才存入
userMap.putIfAbsent("001", "张三");
`
取数据:直接get,和普通HashMap一样简单
取数据的方法和普通HashMap几乎没区别,直接传key就能拿到对应的值,比如要查用户ID“001”的姓名:
`java
String userName = userMap.get("001");
`
而且ConcurrentHashMap的读操作是完全不加锁的,多个线程同时读数据,完全不会有冲突,就算有线程在写,读也能拿到最新的结果,效率特别高,就像小卖部的顾客都在看货架上的商品,不管收银台有没有人结账,都不会互相干扰。
改数据:用replace,避免“读改写”的坑
很多时候我们要先查数据,再修改,比如先查用户“001”的姓名,改成“张三(新)”,如果用普通写法:
`java
// 先读,再改,再写——多线程下会出问题!
String name = userMap.get("001");
userMap.put("001", name + "(新)");
`
这时候如果有两个线程同时读同一个key,可能都拿到旧值,最后覆盖成同一个结果,就像两个服务员同时看了顾客的订单,都改了备注,最后只改了一次。用ConcurrentHashMap的replace方法就能解决,它是原子操作,要么成功替换,要么失败,比如:
`java
// 只有当前值是"张三"时,才替换成"张三(新)"
userMap.replace("001", "张三", "张三(新)");
`
这样就不会出现多线程改数据错乱的问题了。
用它的注意事项
最后说两个新手容易踩的小坑:第一,别随便用它来存超大数量的临时数据,它的分区设计会有一点点额外开销,要是单线程用,普通HashMap效率更高;第二,它的key和value都不能是null,要是你存null,会直接报空指针,就像小卖部不接受空的商品和空的钱,一定要注意这点。
总的来说,ConcurrentHashMap就是Java为多线程场景量身定做的“安全高效的HashMap”,日常开发里只要碰到多线程共享数据的需求,用它准没错,熟练掌握它的几个核心方法,就能解决大部分多线程数据操作的问题。