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用法
- 尽量测试
阅读每个类型的源码,并不是必要的,只是在有空或者必要的时候才做。
java的ConCurrentHashMap的更多相关文章
- java.util.ConcurrentHashMap (JDK 1.8)
1.1 java.util.ConcurrentHashMap继承结构 ConcurrentHashMap和HashMap的实现有很大的相似性,建议先看HashMap源码,再来理解Concurrent ...
- Java:ConcurrentHashMap类小记-3(JDK8)
Java:ConcurrentHashMap类小记-3(JDK8) 结构说明 // 所有数据都存在table中, 只有当第一次插入时才会被加载,扩容时总是以2的倍数进行 transient volat ...
- Java:ConcurrentHashMap类小记-2(JDK7)
Java:ConcurrentHashMap类小记-2(JDK7) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentH ...
- Java:ConcurrentHashMap类小记-1(概述)
Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...
- Java集合---ConcurrentHashMap原理分析
集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主 ...
- Java集合——ConcurrentHashMap
集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主 ...
- Java 线程 — ConcurrentHashMap
ConcurrentHashMap ConcurrentHashMap 结构 采用了分段锁的方法提高COncurrentHashMap并发,一个map里面有一个Segment数组--即多个Segmen ...
- Java:ConcurrentHashMap是弱一致的
本文将用到Java内存模型的happens-before偏序关系(下文将简称为hb)以及ConcurrentHashMap的底层模型相关的知识.happens-before相关内容参见:JLS §17 ...
- Java:ConcurrentHashMap支持完全并发的读
ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁.(事实上,ConcurrentHashMap支持完全并发的读以及一定程度并发的写.)如果使用传统的技术,如HashMa ...
- Java:ConcurrentHashMap的锁分段技术
术语定义 术语 英文 解释 哈希算法 hash algorithm 是一种将任意内容的输入转换成相同长度输出的加密方式,其输出被称为哈希值. 哈希表 hash table 根据设定的哈希函数H(ke ...
随机推荐
- 《Effective C++》第三版-0. 导读(Introduction)
目录 术语(Terminology) 命名习惯(Naming Conventions) 关于线程(Threading Consideration) TR1和Boost 术语(Terminology) ...
- HEOI2024省选游记
day 0 上午 没让不跑操的同学帮忙带着包 所以就直接背着跑的操,相当难受 吃完早饭就拿手机来机房了 不得不说看得出来 huge 这届确实打算换一种教学思路 以来就先强调了一堆意料之外的东西 包括但 ...
- virutalenvwrapper安装和使用
目录 virutalenvwrapper安装和使用 目的: 1.安装pip 2.安装virutalenv和virutalenvwrapper 3.配置环境变量 4. 创建虚拟环境 5.列出全部的虚拟环 ...
- 如何在低代码平台中引用 JavaScript ?
引言 在当今快速发展的数字化时代,企业对业务应用的需求日益复杂且多元.低代码开发平台作为一个创新的解决方案,以直观易用的设计理念,打破了传统的编程壁垒,让非技术人员也能轻松构建功能完备的Web应用程序 ...
- Go-Zero技能提升:深度探究goctl的妙用,轻松应对微服务开发挑战!(三)
前言 有位同学在群里说:"Go-Zero官方文档太简洁了,对小白有点不友好.好奇你们是怎么学习的?项目是怎么封装的?有什么提高开发效率的技巧吗?". 来来来,这期内容给你安排上,先 ...
- iceoryx源码阅读(八)——IPC通信机制
目录 1 整体结构 2 序列化与反序列化 3 类Unix系统的实现 3.1 发送函数send 3.2 接收函数receive 4 Windows系统的实现 4.1 发送函数send 4.2 接收函数r ...
- 鸿蒙Blank
Blank组件占剩余空间,类似占位组件一样
- Vue3 echarts 组件化使用 resizeObserver
点击查看代码 const resizeObserver = ref(null); //进行初始化和监听窗口变化 onMounted(async () => { await nextTick(() ...
- Asp-Net-Core开发笔记:使用ActionFilterAttribute实现非侵入式的参数校验
前言 在现代应用开发中,确保API的安全性和可靠性至关重要. 面向切面编程(AOP)通过将横切关注点(如验证.日志记录.异常处理)与核心业务逻辑分离,极大地提升了代码的模块化和可维护性. 在ASP.N ...
- C# winfrom 局域网版多人成语接龙(二)
功能基本上是完成了,要两个人完才好玩,目前 倒计时,每组游戏玩家数量这些控制变量,都是写死再代码里的,等以后想改的时候再改,这个项目核心的功能算是实现了,但还可以扩展,比如记录一下用户的游戏数据,答对 ...