首先需要明确的是,不管使用那种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. Oracle 正则表达式函数-REGEXP_REPLACE

    背景 当初写oracle的一个存储过程,以前不知道sql里也有正则表达式,关于正则表达式教程很多了,这里只是记录下Oracle也有这个功能,下次再有类似需求用这个处理的确方便很多. 想起存储过程,就想 ...

  2. 一步步实现windows版ijkplayer系列文章之四——windows下编译ijkplyer版ffmpeg

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  3. [HTML]点击按钮,页面总是跳回顶端的解决方法(Clicking an button,always resets the view to top of page)

    1 前言 当网页页面较长或者表单较多时,右侧会出现滚动条,然而经常会出现点击底部的<button>按钮或者<a>超链接,会出现点击后,当前页面会回到顶端. 2 方案 例如样例代 ...

  4. how to detect circles and rectangle?

    opencv中对圆检测的函数为:HoughCircles(src_gray,circles,CV_HOUGHT_GRADIENT,1,src_gray.cols/8,200,100,0,0) circ ...

  5. jQuery Ajax实例各种使用方法详解

    在jquery中ajax实现方法分类很多种,如有:load.jQuery.get.jQuery.post.jQuery.getScript.jQuery Ajax 事件.jQuery.ajaxSetu ...

  6. 上传本地文件到github仓库基本操作

    上传文件到github时老师忘记指令,或者总是出一些错,每次都要百度浪费时间,因此将常用操作指令归纳卸载这里,以后再也不要担心百度找帖子了... 第一步:新建仓库 新建仓库步骤省略,最后我们得到一个仓 ...

  7. 【ES】学习6-多字段搜索1

    本系列的笔记都来自:https://elasticsearch.cn/book/elasticsearch_definitive_guide_2.x/multi-field-search.html 下 ...

  8. spoj New Distinct Substrings

    vjudge原地爆炸... 题意:求一个字符串不同的子串的个数 策略:后缀数组 利用后缀数组的sa和height两个功能强大的数组,我们可以实现上述操作 首先有个很显然的结论:一个字符串的所有子串=它 ...

  9. locust的安装与使用

    Contents Locust这一款开源性能测试工具.然而,当前在网络上针对Locust的教程极少,不管是中文还是英文,基本都是介绍安装方法和简单的测试案例演示,但对于较复杂测试场景的案例演示却基本没 ...

  10. [转] 理解 JavaScript 中的 Array.prototype.slice.apply(arguments)

    假如你是一个 JavaScript 开发者,你可能见到过 Array.prototype.slice.apply(arguments) 这样的用法,然后你会问,这么写是什么意思呢? 这个语法其实不难理 ...