一般的应用的编程,用到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. dotnet 统信 UOS 运行 UNO FrameBuffer 应用错误 Failed to open FrameBuffer device

    本文记录在 UOS 统信系统上运行 UNO 的基于 Skia 的 FrameBuffer 应用报错问题,错误提示是 Unhandled exception. System.InvalidOperati ...

  2. WPF 已知问题 某些设备上的应用在 WindowChromeWorker 抛出 System.OverflowException 异常

    准确来说,这个不算是 WPF 的问题,而是系统等的问题.在某些设备上的使用了 WindowChrome 功能的 WPF 应用,将在运行过程,在 WindowChromeWorker 类里面抛出 Sys ...

  3. 2019-8-31-C#-转换类型和字符串

    title author date CreateTime categories C# 转换类型和字符串 lindexi 2019-08-31 16:55:58 +0800 2018-2-13 17:2 ...

  4. Microsoft SQL Server 自定义函数整理大全

    https://www.cnblogs.com/ybb521/p/3210271.html

  5. 他又来了,.net开源智能家居之小米米家的c#原生sdk【MiHome.Net】1.0.0发布,快来打造你的私人智能家居吧

    背景介绍 hi 大家好,我是三合,作为一个非著名懒人,智能家居简直刚需,在上一篇文章他来了他来了,.net开源智能家居之苹果HomeKit的c#原生sdk[Homekit.Net]1.0.0发布,快来 ...

  6. SpringBoot项目预加载数据——ApplicationRunner、CommandLineRunner、InitializingBean 、@PostConstruct区别

    0.参考.业务需求 参考: https://www.cnblogs.com/java-chen-hao/p/11835120.html#_label1 https://zhuanlan.zhihu.c ...

  7. 使用WebSocket实现实时多人答题对战游戏

    前言 前两章教程,我们使用WebSocket的基础特性打造了一个小小聊天室,并在第二章对其进行了集群化改造. 系列教程回顾: [WebSocket]第一章:手把手搭建WebSocket多人在线聊天室( ...

  8. R5_ES读写流程

    基本概念 refresh:es接收数据请求时先存入ES进程中的内存 Buffer ,默认每隔一秒(index.refresh_interval:1s)会从内存buffer中将数据写入 os cache ...

  9. 密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg)

    密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg) 问题: 解决: cd /etc/opt sudo cp trusted.gpg trusted.gpg. ...

  10. Delaunay三角剖分实现

    参考文章:https://www.cnblogs.com/zhiyishou/p/4430017.html 本文使用逐点插入法进行剖分,并使用Unity3D实现. 通过阅读文章<Triangul ...