一般的应用的编程,用到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的更多相关文章

  1. java.util.ConcurrentHashMap (JDK 1.8)

    1.1 java.util.ConcurrentHashMap继承结构 ConcurrentHashMap和HashMap的实现有很大的相似性,建议先看HashMap源码,再来理解Concurrent ...

  2. Java:ConcurrentHashMap类小记-3(JDK8)

    Java:ConcurrentHashMap类小记-3(JDK8) 结构说明 // 所有数据都存在table中, 只有当第一次插入时才会被加载,扩容时总是以2的倍数进行 transient volat ...

  3. Java:ConcurrentHashMap类小记-2(JDK7)

    Java:ConcurrentHashMap类小记-2(JDK7) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentH ...

  4. Java:ConcurrentHashMap类小记-1(概述)

    Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...

  5. Java集合---ConcurrentHashMap原理分析

    集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主 ...

  6. Java集合——ConcurrentHashMap

    集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap).这篇文章主 ...

  7. Java 线程 — ConcurrentHashMap

    ConcurrentHashMap ConcurrentHashMap 结构 采用了分段锁的方法提高COncurrentHashMap并发,一个map里面有一个Segment数组--即多个Segmen ...

  8. Java:ConcurrentHashMap是弱一致的

    本文将用到Java内存模型的happens-before偏序关系(下文将简称为hb)以及ConcurrentHashMap的底层模型相关的知识.happens-before相关内容参见:JLS §17 ...

  9. Java:ConcurrentHashMap支持完全并发的读

    ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁.(事实上,ConcurrentHashMap支持完全并发的读以及一定程度并发的写.)如果使用传统的技术,如HashMa ...

  10. Java:ConcurrentHashMap的锁分段技术

    术语定义 术语 英文 解释 哈希算法 hash algorithm 是一种将任意内容的输入转换成相同长度输出的加密方式,其输出被称为哈希值.  哈希表 hash table 根据设定的哈希函数H(ke ...

随机推荐

  1. 2019-11-29-C#-如何写-DEBUG-输出

    title author date CreateTime categories C# 如何写 DEBUG 输出 lindexi 2019-11-29 08:28:35 +0800 2018-2-13 ...

  2. 超好用的 Redis GUI 工具,你值得拥有

    超好用的 Redis GUI 工具,你值得拥有 提供原生的性能,并且比使用 Electron 等 Web 技术开发的同等应用程序消耗的资源少得多. 下载地址:http://www.redisant.c ...

  3. netcore热插拔dll

    项目中有有些场景用到反射挺多的,用到了反射就离不开dll的加载.此demo适用于通过反射dll运行项目中加载和删除,不影响项目. ConsoleApp: 1 using AppClassLibrary ...

  4. 【web安全】隐藏nginx头文件信息

    摘要 Nginx作为开源web中间件,被广泛应用.因此源编译或者yum安装,都会带有其原有的nginx版本.很容易被针对,因此,通过修改nginx的源码.隐藏nginx版本和头部信息,保障nginx的 ...

  5. python教程5:函数编程

    函数编程 特性: 1.减少重复代码 2.让程序变的可扩展 3.使程序变得易维护 定义:  默认参数 要求:默认参数放在其他参数右侧 指定参数(调用的时候) 正常情况下,给函数传参数要按顺序,如果不想按 ...

  6. inno Setup 打包Java exe可执行文件和MySQL数据库,无需额外配置实现一键傻瓜式安装

    前言 出现有需要打包 Java 应用和 Mysql数据库成一个安装包给出去的需求,这里我把整个打包的流程整理一下. 环境 JDK17; MySQL 5.7; 流程 Jpackage打包EXE Jpac ...

  7. JDK源码阅读-------自学笔记(三)(java.lang.String String用法和描述浅析)

    一.源码特点 final约束,使得String不能被继承,内部其他也不能被继承 String用来表示字符串,或字符序列,序列即为数组 内建数组private final char value[];但是 ...

  8. WPF 滚动条ScrollViewer样式记录

    WPF 应用程序中有两个支持滚动的预定义元素:ScrollBar 和 ScrollViewer. ScrollViewer 控件封装了水平和垂直 ScrollBar 元素以及一个内容容器(如 Pane ...

  9. Qt-FFmpeg开发-打开本地摄像头录制视频(7)

    音视频/FFmpeg #Qt Qt-FFmpeg开发-打开本地摄像头录制视频[软解码+ OpenGL显示YUV] 目录 音视频/FFmpeg #Qt Qt-FFmpeg开发-打开本地摄像头录制视频[软 ...

  10. ra6m3之adc踩坑日志(基于rt-thread )

    问题描述 1.已经用fsp工具正确配置Pins 2.已经用fsp工具正确配置Stacks 3.问题是根本没有在RT-Thread Studio看到HAL相关的ADC代码,也没看到Settings里边的 ...