Hashtable与ConcurrentHashMap源码分析

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

 

文章目录

 


前言

提示:Hashtable与ConcurrentHashMap锁的区别是什么?如何实现线程安全的?


提示:以下是本篇文章正文内容,下面案例可供参考

一、Hashtable与ConcurrentHashMap 包位置

Hashtable                         package java.util;

ConcurrentHashMap        package   java.util.concurrent; //并发工具包,有很多线程安全工具

 

二、线程安全实现原理

1.Hashtable

通过synchronized 锁住当前实例来实现线程安全

代码如下(源码):

//Entry extend Map.Entry<k,v>  可以看到数组
private transient Entry<?,?>[] table;
.............
//Map put操作会对Hashtable 实例上锁  其他线程不能获得锁 
 //因此synchronized修饰的同步方法或块只有一个线程能操作  其他线程等待锁后才能操作
//synchronized 是对象锁 独占锁 是重量级的
public synchronized V put(K key, V value) {
    // Make sure the value is not null  value不为空
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    //根据 key的hash码计算存放位置
    int hash = key.hashCode();
    //计算数组下标
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    //看看 key键的entry是否已经有
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            //找到了并且覆盖key相同的entry的value值
            entry.value = value;
            return old;
        }
    }
    //没有相同的key 则创建一个entry
    addEntry(hash, key, value, index);
    return null;
}
..........

2.ConcurrentHashMap

ConcurrentHashMap put 流程是这样的;  //cas保证线程安全操作

1.查看 put的key 的entry<k,v>是否已存在? 不存在创建

2.entry已经存在了, 那么是否其他线程修改了? 是的话 重读table 从头开始遍历操作 ->1

3.table值是最新的 那么 synchronized锁住更新的entry 然后更新它

 

代码如下(源码):

/**
 *组合对象 Map.entry<K,V>的子类
 *volatile 修饰保证多线程  可见性 指令禁止重排  但不具有原子性
 */
transient volatile Node<K,V>[] table;
//put并未使用 Synchronized 修饰, 那么如何保证线程安全呢?
public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    //计算key hash码
    int hash = spread(key.hashCode());
    int binCount = 0;
    // Map.entry<k,v>数组 table   这里死循环 break跳出
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
           //数组不存在 则创建数组
            tab = initTable();
            
            // 根据 hash 查找 entry<k,v> 是不是不存在
            // 不存在进行添加
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //不存在就做cas 创建一个entry 放到数组里
            //cas 比较并交换 如果其他线程没改变值这里就创建成功了
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //key的节点是 转发节点 
        else if ((fh = f.hash) == MOVED)
            //tab值重读,进行下一轮 cas 可能是其他线程进行的更新操作
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //对 数组tab的 f进行更新操作  
            //synchronized 锁住更新对象 保证更新是同步的
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}
............

 


总结

Hashtable 使用的是 synchronized 修饰方法或块保证线程安全, 锁住了整个对象实例;

 

ConcurrentHashMap  首先使用 valotile 修饰 Node<K,V>[] table 保证多线程可以读取值(可见性)

 

然后比较需要更新的 entry 其他线程修改了 则重读table数组

然后再重新cas 直到发现待更新的entry为最新

给他上锁 synchronized (f) 更新值.

ConcurrentHashMap 分段锁锁的对象为 Node<K,V> ,

其他线程还是能够操作数组其他的Node<K,V>

 

上一篇:数据结构和算法(九)哈希表 HashTable


下一篇:**HashMap与HashTable与ConcurrentHashMap**比较