hashmap、hashtable、ConCurrentHashMap分析

来源:互联网 发布:网络运维工程师招聘 编辑:程序博客网 时间:2024/06/10 08:23

优秀博文:http://www.cnblogs.com/yydcdut/p/3959815.html

线程不安全的hashmap

hashmap是线程不安全,在多线程情况下,使用hashmap的put操作会引起死循环。所以在并发情况下不能使用HashMap

线程安全的hashtable

但是在多线程情况下效率极低。因为synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,所有访问hashtable的线程都对同一个锁进行竞争。

线程安全且效率高的ConCurrentHashMap

采用了锁分离技术,每个段都有一个锁,所以可以在一定程度上保证写并发。读操作不需要加锁,所以可以实现完全读并发。

内部结构

这里写图片描述

ConCurrentHashMap内部使用段(Segment),每个段都是一个小的hashtable(其中包含多个HashEntry[])。每个段都有自己独立的锁,只要多个修改操作发生在不同的段上,就可以实现并发。(读操作可以完全实现并发,因为读不加锁)
但是!
有些方法需要跨段,如size() containsValue()。这些方法可能需要锁定整个表。因此,需要按顺序锁定所有段,操作完毕后,又需要按顺序释放所有锁。(如果这里加锁以及释放锁没有按顺序,可能会出现死锁。在ConCurrentHashMap内部,段数组是声明为final的,但是这并不保证段数组成员也是final的)

源码分析

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

如上所示,除value之外,其他成员都是final的,这意味者只能从hash表的头部添加或删除节点(因为需要需改next引用值,所有节点的修改只能从头部开始,put操作也一律添加到头部)
为了确保读操作能够读取到最新的值,将value设置为volatile,这就避免了加锁。

但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。

get方法:

public V get(Object key) {    int hash = hash(key); // throws NullPointerException if key null    return segmentFor(hash).get(key, hash);}

可见,get方法并没有加锁,交给segment去寻找。

V get(Object key, int hash) {        if (count != 0) {             HashEntry<K,V> e = getFirst(hash);             while (e != null) {                if (e.hash == hash && key.equals(e.key)) {                    V v = e.value;                    if (v != null)                         return v;                    return readValueUnderLock(e);                 }                e = e.next;            }        }        return null;}

它也没有用锁实现数据同步。归根结底就是因为value设置为volatile
get操作第一步访问count变量(该变量声明为volatile)。所有的结构修改操作都会在最后一步写count变量,(这就保证了get操作能够获取到最新的值)

原创粉丝点击