提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、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>