Hashtable与ConcurrentHashMap源码分析
2021/6/4 22:21:15
本文主要是介绍Hashtable与ConcurrentHashMap源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、Hashtable与ConcurrentHashMap 包位置
- 二、线程安全实现原理
- 1.Hashtable
- 2.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与ConcurrentHashMap源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享