首先需要明确的是,不管使用那种Map,都不能保证公共混合调用的线程安全,只能保证单条操作的线程安全,在这一点上各Map不存在优劣。

前文中简单说过HashTable和synchronizedMap,其实这两个类不需要说太多,把代码贴一下相信看过Java多线程的就能很容易理解了。

HashTable

HashTable的话,实现这个样子的。可以看到的是,对于Hash表的所有操作,HashTable都加了锁,但也只能保证单条操作的线程安全。

public synchronized V get(Object key) {
// 省略实现
}
public synchronized V put(K key, V value) {
// 省略实现
}

synchronizedMap

synchronizedMap的实现如下,没直接在方法上加,尽管其实质与HashTable是等效的,也同样有HashTable的缺陷,但synchronizedMap给用户留下了选择的空间:用户可以在不需要加锁时直接操作原始Map,在实际编码时就可以基于这点进行优化。

// synchronizedMap方法
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
// SynchronizedMap类
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
} SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
} public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
} public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
// 省略其他方法
}

ConcurrentHashMap

提高安全HashMap的并发性的方法,可以通过减小锁粒度的方式,不对整个Hash表加锁,而是对每个bucket加锁,甚至用锁池,每个锁维护几个bucket,让Map的不同部分可以被多个线程访问,不过这样的方式会让对整体集合操作的方法的实现更加困难。Java7中的ConcurrentHashMap就通过Segment引入了这个分段加锁概念,但Java8由于上述困难更改了机制,引入了红黑树结构,去掉了Segment。

JDK1.8的改进后,ConcurrentHashMap的写性能有10%左右的降低,但读性能有了很大提升。主要是将过于集中的hash节点的效率从O(N)提高到了O(LOGN)。

ConcurrentHashMap利用了CAS进行实现,从而以乐观锁的方式实现了线程安全的HashMap,concurrentHashMap的源码很复杂,一些方法的实现思路如下:

Java8的ConcurrentHashMap的数据结构实现思路大概为,对于Hash表中每一个节点,其数据结构可以为单节点,链表数组或红黑树,随着节点中元素增加而改变。(改变方法见treeifyBin)。

put()方法

  • hash数组是否为空,为空则先调用initTable()方法进行初始化
  • 如果hash数组已经初始化了,则根据hash值找到对应的数组下标,如果对应节点为空,通过cas方式直接插入
  • 如果数组已经扩容,则进行数据迁移
  • 如果数组该位置已经有值了,则需要对该节点加锁并进行数据插入操作,仅对一个节点加锁,其锁粒度实际上比Java7中Segment实现更小。此时如果该节点是链表结构,则遍历链表,插入数据;如果如果该节点是红黑树结构,则调用红黑树的插值方法插入新值
  • 针对链表结构,如果插入新元素后,hash数组长度超过阈值,则需要调用treeifyBin()方法进行扩容或者是将链表转换为红黑树

initTable()方法

  • 当table不存在,开始自旋。
  • 利用CAS操作将sizeCtl属性设置为-1,表示本线程正对数组初始化,阻止其他线程的初始化。
  • 进行常规的初始化操作,扩容阈值为数组容量的75%。
  • 将sizeCtl设置成扩容阈值,结束初始化。

treeifyBin()方法

该方法用于对数组链表扩容,或将链表结构转化为红黑树,一个节点的元素个数大于链表阈值(默认8)时,如果数组链表长度小于红黑树阈值(默认64),则对数组链表扩容,否则将该节点转换为红黑树。

transfer(),helpTransfer(),tryPresize()方法

这些方法负责hash表扩容,由于要通过CAS实现线程安全,代码十分复杂。大概思路为,原数组长度为n,则产生n个迁移任务,让每一个线程负责一个小任务,之后监测是否有其他没做完的任务,帮助迁移。

get()方法

get方法不涉及CAS操作,实现较为简单,计算hash值,找到对应节点进行判断:

  • 该位置为null返回null。
  • 该位置节点为所求值,返回值。
  • 该位置节点hash值小于0,说明在扩容,或者为红黑树,使用find方法。
  • 以上都不满足,该位置为链表,遍历搜索。

性能

目前多线程环境下ConcurrentMap的性能有很高的优越性,通常情况下,如果你的Map处于多读少写的场景,优先考虑ConcurrentMap,但在多写少读的情境中,由于资源竞争激烈,CAS自旋可能导致ConcurrentMap性能不如synchronizedMap。

参考文献

Collections.synchronizedMap()、ConcurrentHashMap、Hashtable之间的区别

Java8 ConcurrentHashMap详解

浅谈Java8中的ConcurrentHashMap

SynchronizedMap

Java容器:HashTable, synchronizedMap与ConcurrentHashMap的更多相关文章

  1. Java中SynchronizedMap与ConcurrentHashMap的对比

    如何使用 概述 ConcurrentHashMap: 线程安全: 其将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都 ...

  2. 测试HashTable、Collections.synchronizedMap和ConcurrentHashMap的性能

        对于map的并发操作有HashTable.Collections.synchronizedMap和ConcurrentHashMap三种,到底性能如何呢? 测试代码: package com. ...

  3. Hashtable、synchronizedMap、ConcurrentHashMap 比较

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp18 Hashtable.synchronizedMap.Concurren ...

  4. Collections.synchronizedMap()、ConcurrentHashMap、Hashtable之间的区别

    为什么要比较Hashtable.SynchronizedMap().ConcurrentHashMap之间的关系?因为常用的HashMap是非线程安全的,不能满足在多线程高并发场景下的需求. 那么为什 ...

  5. 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识

    沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...

  6. Java - 容器详解

    一.ArrayList 长度可变数组,类似于c++ STL中的vector. 元素以线性方式连续存储,内部允许存放重复元素. 允许对元素进行随机的快速访问,但是向ArrayList中插入和删除元素的速 ...

  7. SynchronizedMap和ConcurrentHashMap 区别

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt215 SynchronizedMap和ConcurrentHashMap的深 ...

  8. Java容器的常见问题

    记录Java容器中的常见概念和原理 参考: https://github.com/wangzhiwubigdata/God-Of-BigData#三Java并发容器 https://blog.csdn ...

  9. JAVA学习:HashMap 和 ConcurrentHashMap

     一.最基本的HashMap 和 ConcurrentHashMap 1.HashMap的结构和底层原理:由数组和链表组成,数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry ...

随机推荐

  1. saltstack中如何实现多个master来管理minion

    背景: 公司有多个部门,有一些机器有本部门的业务,这些机器也有其他部门的业务,所以本部门需要一个master服务器来管理这批机器,其他部门也需要一个master服务器来管理这个机器,所以就需要多个ma ...

  2. QA CodeDiff做什么?什么时间做?

    一.QA CodeDiff都在做什么 1.防止开发合并代码出错.要不删除了别人的要不删除了自己的,比如代码冲突后简单的选择使用他人或自己: 2.开发夹杂私货,在不通知QA的情况下私自修改bug或增加功 ...

  3. 缓存之EHCache(一)

    源文: http://blog.csdn.net/l271640625/article/details/20528573 一.简介 非常简单,而且易用.     ehcache 是一个非常轻量级的缓存 ...

  4. css 中两个class之间没有空格与有空格有什么区别

    第一个匹配: <div class="ul item"></div>:无法匹配:<div class="ul"></d ...

  5. windows10+mysql8.0.11zip安装

    准备: MySQL8.0 Windows zip包下载地址:https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.11-winx64.zip 环境: ...

  6. CF1081A

    CF1081A 题意: 从

  7. zoj1716简单的二维树状数组

    问一个矩形框在一个大矩形内最多能围几个给定的点 都不用排序,先把所有的点加入树状数组,再直接枚举大矩形的每个格子即可 #include <iostream> #include <st ...

  8. 步步为营-20-XML

    说明:可扩展标记语言 eXtensible Markup Language--区分大小写 涉及到的知识点:DOM 文档对象模型 文本文件存储数据缺点:1,不易读取.2,易乱码 1 通过代码创建一个xm ...

  9. webpack - require 概要

    webpack 是一个预编译模块打包工具,它只会对使用到的模块进行打包. 一个模块是否被使用?可以根据该模块是否被 require 来判断.如果require时指定的是具体的模块名称与正确的路径,那么 ...

  10. .NetCore下使用Prometheus实现系统监控和警报 (五)进阶自定义收集指标 之 Counter

    Prometheus下面定了四种类型的收集方式,下面我们主要来来说下Counter的使用 Nuget导入Prometheus.AspNetCore包 下面先来看下我的Prometheus配置,这里我没 ...