在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:ConcurrentMap。本篇文章主要关注ConcurrentMap接口以及它的Hash版本的实现ConcurrentHashMap。

一、ConcurrentMap

与Map接口相比,ConcurrentMap多了4个方法:

1)putIfAbsent方法:如果key不存在,添加key-value。方法会返回与key关联的value

V putIfAbsent(K key, V value);

2)remove方法

boolean remove(Object key, Object value);

Map接口中也有一个remove方法:

V remove(Object key);

ConcurrentMap中的remove方法需要比较原有的value和参数中的value是否一致,只有一致才会删除。

3)Replace方法:有2个重载

boolean replace(K key, V oldValue, V newValue);
V replace(K key, V value);

两个重载的区别和2)中的两个remove方法的区别很类似,多了一个检查value一致。

二、ConcurrentHashMap

ConcurrentHashMap和HashMap类似,这里重点关注的是如何实现线程安全,也就是如何加锁。

对于HashMap来说,有一个Entry数组,根据Key的hash值对数组长度取模得到数组下标,找到Entry,遍历整个Entry链表,用equals比较来确定key所在的Entry。

ConcurrentHashMap的基本思想是采取分块的方式加锁,分块数由参数“concurrencyLevel”来决定(和HashMap中的“initialCapacity”类似,实际块数是第一个大于concurrencyLevel的2的n次方)。每个分块被称为Segment,Segment的索引方式和HashMap中的Entry索引方式一致(hash值对数组长度取模)。

对Segment加锁的方式很简单,直接把Segment定义为ReentrantLock的子类。同时Segment又是一个特定实现的hash table。

static final class Segment<K,V> extends ReentrantLock implements Serializable

下面分析ConcurrentHashMap读写时如何加锁。

首先是读操作类的方法,来看get方法:

public V get(Object key) {
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}

可以看到,读取的时候没有调用的Segment的获取锁的方法,而是通过hash值定位到Entry,然后遍历Entry的链表。

为什么这里不用加锁呢?看看HashEntry的代码就会明白了。

    static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;

value和next属性是带有volatile修饰符的,可以大胆放心的遍历和比较。

接着是写操作,写操作是肯定要加锁的。因为Segment可以看成是一个hash table,因此ConcurrentHashMap直接调用Segment的对应的写入方法如put,replace等。

比如put方法

    public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}

因此这里直接关注Segment的对应写操作方法即可。在每个写操作的方法开头都这样的类似代码:

        final V remove(Object key, int hash, Object value) {
if (!tryLock())
scanAndLock(key, hash);
            HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value

也就是,首先尝试获取锁,如果成功则会带锁继续操作,失败则要通过scanAndLock或scanAndLockForPut获取锁,因此这里关注的重点也就转移到这两个方法了。

按照多线程环境的规则,如果尝试获取锁失败的话就会进入阻塞等待状态,那么这两个方法的作用应该是类似的。

        private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
if (e == null) {
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
else if (key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}

这两个方法的逻辑:在等待的时候闲着没事儿干把该做好的准备做好,查找一下目标entry,如果是新建entry就把entry创建好,然后如果一切没问题就用lock()方法把自己给阻塞了,也就是做好准备然后去等着了。

源码阅读 - java.util.concurrent (三)ConcurrentHashMap的更多相关文章

  1. 源码阅读 - java.util.concurrent (二)CAS

    背景 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. ...

  2. 源码阅读 - java.util.concurrent (一)

    java.util.concurrent这个包大致可以分为五个部分: Aomic数据类型 这部分都被放在java.util.concurrent.atomic这个包里面,实现了原子化操作的数据类型,包 ...

  3. 源码阅读 - java.util.concurrent (四)CyclicBarrier

    CyclicBarrier是一个用于线程同步的辅助类,它允许一组线程等待彼此,直到所有线程都到达集合点,然后执行某个设定的任务. 举个例子:几个人约定了某个地方集中,然后一起出发去旅行.每个参与的人就 ...

  4. Java源码之 java.util.concurrent 学习笔记01

    准备花点时间看看 java.util.concurrent这个包的源代码,来提高自己对Java的认识,努力~~~ 参阅了@梧留柒的博客!边看源码,边通过前辈的博客学习! 包下的代码结构分类: 1.ja ...

  5. 源码(09) -- java.util.Arrays

    java.util.Arrays 源码分析 ------------------------------------------------------------------------------ ...

  6. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  7. 源码(03) -- java.util.Collection<E>

     java.util.Collection<E> 源码分析(JDK1.7) -------------------------------------------------------- ...

  8. J.U.C并发框架源码阅读(十二)ConcurrentHashMap

    基于版本jdk1.7.0_80 java.util.concurrent.ConcurrentHashMap 代码如下 /* * ORACLE PROPRIETARY/CONFIDENTIAL. Us ...

  9. JDK1.8源码(五)——java.util.Vector类

    JDK1.8源码(五)--java.lang. https://www.cnblogs.com/IT-CPC/p/10897559.html

随机推荐

  1. C# Windows服务以指定用户运行

    参考一下 https://bbs.csdn.net/topics/330151879 服务程序以Local System安装运行没问题,但用这个账户运行的服务无法访问局域网共享资源,比较麻烦,所以想指 ...

  2. qmake 时复制文件(自动在编译前做一些操作,且写在.pro文件里)

    有时在编译前需要准备一些文件,例如修改了 QtCreator 的编译输出目录: Build & Run > Default build directory,使用 Promote 后需要在 ...

  3. Win8 Metro(C#)数字图像处理--2.38Hough变换直线检测

    原文:Win8 Metro(C#)数字图像处理--2.38Hough变换直线检测  [函数名称] Hough 变换直线检测         HoughLineDetect(WriteableBit ...

  4. linux服务脚本编写

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 3 ...

  5. IT回忆录-1

    作为80后,差不多算是最开始一批接触互联网的人了.从用56K的猫拨号上网开始,不断地见证计算机和互联网的变化. 哥哥中考没考上,后来就去跟老师学计算机了.等他学完以后,我们家有了第一台电脑. 那个电脑 ...

  6. 使用MinGW编译Boost

    1.下载Boost(http://www.boost.org) 我目前用的是1.61.0版本 2.将MinGW下的bin目录完整路径设置到系统环境变量Path中,保证cmd命令行能找到gcc,g++等 ...

  7. CDC-更改数据捕获存储过程 (Transact-SQL)-学习

    背景: 在SQLServer2008之前,对数据变更的捕获通常使用触发器.时间戳等低效高成本的功能来实现,所以很多系统都没有做数据变更或者仅仅对核心表做监控. 适用环境: 仅在SQLServer200 ...

  8. 学习Java,容易被你忽略的小细节(3)

    在<学习Java,容易被你忽略的小细节(2)>中我们已经深入讨论过String类中equals()方法的使用,在本文中我们将进一步讨论equals()方法在其他境况下的使用方法. 首先,我 ...

  9. win7访问部分win2003速度慢

    解决办法: 关闭TCPIP协议的自动优化调整功能,在win7上,以管理员身份运行cmd,输入 netsh interface tcp set global autotuninglevel=disabl ...

  10. 高可用的zookeeper

    Install zookeeper wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.13/zookeeper-3.4.13 ...