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 ...
随机推荐
- 2019-11-29-C#-如何写-DEBUG-输出
title author date CreateTime categories C# 如何写 DEBUG 输出 lindexi 2019-11-29 08:28:35 +0800 2018-2-13 ...
- 超好用的 Redis GUI 工具,你值得拥有
超好用的 Redis GUI 工具,你值得拥有 提供原生的性能,并且比使用 Electron 等 Web 技术开发的同等应用程序消耗的资源少得多. 下载地址:http://www.redisant.c ...
- netcore热插拔dll
项目中有有些场景用到反射挺多的,用到了反射就离不开dll的加载.此demo适用于通过反射dll运行项目中加载和删除,不影响项目. ConsoleApp: 1 using AppClassLibrary ...
- 【web安全】隐藏nginx头文件信息
摘要 Nginx作为开源web中间件,被广泛应用.因此源编译或者yum安装,都会带有其原有的nginx版本.很容易被针对,因此,通过修改nginx的源码.隐藏nginx版本和头部信息,保障nginx的 ...
- python教程5:函数编程
函数编程 特性: 1.减少重复代码 2.让程序变的可扩展 3.使程序变得易维护 定义: 默认参数 要求:默认参数放在其他参数右侧 指定参数(调用的时候) 正常情况下,给函数传参数要按顺序,如果不想按 ...
- inno Setup 打包Java exe可执行文件和MySQL数据库,无需额外配置实现一键傻瓜式安装
前言 出现有需要打包 Java 应用和 Mysql数据库成一个安装包给出去的需求,这里我把整个打包的流程整理一下. 环境 JDK17; MySQL 5.7; 流程 Jpackage打包EXE Jpac ...
- JDK源码阅读-------自学笔记(三)(java.lang.String String用法和描述浅析)
一.源码特点 final约束,使得String不能被继承,内部其他也不能被继承 String用来表示字符串,或字符序列,序列即为数组 内建数组private final char value[];但是 ...
- WPF 滚动条ScrollViewer样式记录
WPF 应用程序中有两个支持滚动的预定义元素:ScrollBar 和 ScrollViewer. ScrollViewer 控件封装了水平和垂直 ScrollBar 元素以及一个内容容器(如 Pane ...
- Qt-FFmpeg开发-打开本地摄像头录制视频(7)
音视频/FFmpeg #Qt Qt-FFmpeg开发-打开本地摄像头录制视频[软解码+ OpenGL显示YUV] 目录 音视频/FFmpeg #Qt Qt-FFmpeg开发-打开本地摄像头录制视频[软 ...
- ra6m3之adc踩坑日志(基于rt-thread )
问题描述 1.已经用fsp工具正确配置Pins 2.已经用fsp工具正确配置Stacks 3.问题是根本没有在RT-Thread Studio看到HAL相关的ADC代码,也没看到Settings里边的 ...