首先需要明确的是,不管使用那种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数据库修改LISTENER的监听端口

    背景 这又是个不作不会死的事情,自己不懂,硬搞,端口换了,后来竟然捣鼓好了.尽量少搞这些事情. 注意点 http://wallimn.iteye.com/blog/1163614 修改配置文件后,需修 ...

  2. Javascript构造函数的继承

    仅供学习参考,原文链接:http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html 今天要介绍 ...

  3. (常用)time,datetime,random,shutil(zipfile,tarfile),sys模块

    a.time模块import time 时间分为三种形式1.时间戳 (时间秒数的表达形式, 从1970年开始)print(time.time())start_time=time.time()time. ...

  4. zabbix监控短信息接口是否正常

    1.创建Web scenarios 2.创建zabbix触发器name:short_message send status is not 100 Expression:{u04zbx01.yaya.c ...

  5. android 手机拍照返回 Intent==null 以及intent.getData==null

    手机拍照第一种情况:private void takePicture(){ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);Si ...

  6. Oracle 网络配置与管理

    [学习目标] Oracle 监听器是一个服务器端程序,用于监听所有来自客户端的请求,并为其提供数 据库服务.因此对监听器的管理与维护相当重要.         本章主要内容是描述对Oracle 监听器 ...

  7. js闭包实例汇总

    本文是通过实例来帮助大家深刻理解js闭包,是篇非常不错的文章,这里推荐给大家,有需要的小伙伴可以参考下 Js闭包 闭包前要了解的知识 1. 函数作用域 (1).Js语言特殊之处在于函数内部可以直接读取 ...

  8. tomcat 输入学习

    Tomcat学习—Tomcat7 修改/webapps/ROOT发布路径(Linux和windows环境) https://blog.csdn.net/u010648555/article/detai ...

  9. 数论-质数 poj2689,阶乘分解,求阶乘的尾零hdu1124, 求尾零为x的最小阶乘

    /* 要求出[1,R]之间的质数会超时,但是要判断[L,R]之间的数是否是素数却不用筛到R 因为要一个合数n的最大质因子不会超过sqrt(n) 所以只要将[2,sqrt(R)]之间的素数筛出来,再用这 ...

  10. cf792b循环链表

    头尾链接一下就好, /* 1 2 3 4 5 6 7:4 5 6 7 1 2 3:2 3 5 6 7 1:5 6 7 1 3:6 7 1 3:1 3 7 */ #include<bits/std ...