从put操作看ConcurrentHashMap如何解决线程安全问题
创作时间:
作者:
@小白创作中心
从put操作看ConcurrentHashMap如何解决线程安全问题
引用
CSDN
1.
https://blog.csdn.net/weixin_43810802/article/details/125089296
在多线程环境下,HashMap的线程不安全性可能导致数据丢失等问题。本文通过对比分析HashMap和ConcurrentHashMap的put操作实现,详细解释了ConcurrentHashMap如何使用CAS和自旋锁来保证线程安全。
HashMap的线程不安全性
HashMap(JDK 1.8)在多线程环境下是不安全的,其中一个典型体现是put方法的实现。我们来看一下put方法的大致逻辑:
如果原来table中没有相同hash值的节点,就会创建一个新的节点。假设线程A和线程B同时执行put操作,且key的hash值相同,那么它们会同时执行以下代码:
if ((p = tab[i = (n - 1) & hash]) == null)
假设之前没有hash冲突的节点存在,那么A和B线程就会都执行:
tab[i] = newNode(hash, key, value, null);
这样后执行的线程就会覆盖先执行的线程,导致数据丢失。
为了验证这个现象,我们可以编写如下测试代码:
public class TestHashMapThreadSafe {
public static void main(String[] args) throws InterruptedException {
HashMap<Integer, String> map = new HashMap<>();
Thread thread1 = new Thread(() -> {
map.put(1, "a");
}, "A");
thread1.start();
Thread thread2 = new Thread(() -> {
map.put(17, "b");
}, "B");
thread2.start();
Thread.sleep(1000 * 5);
System.out.println(map);
}
}
为了让效果更明显,我们需要修改put方法的源码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null){
if(key instanceof Integer){
Integer intKey = (Integer) key;
if(intKey == 1 || intKey == 17){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " with key==" + key + " try to new node");
}
}
tab[i] = newNode(hash, key, value, null);
}
...
}
运行结果如下:
A with key==1 try to new node
B with key==17 try to new node
{17=b}
可以看到,最终只有一个元素,显然是有问题的。
ConcurrentHashMap的解决方案
ConcurrentHashMap是如何解决上面这个问题的呢?它使用了CAS(Compare and Swap)和自旋锁。自旋锁就是外面那个for循环,失败了就重试直到退出。
我们来看一下ConcurrentHashMap的putVal方法:
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
if(key instanceof Integer){
Integer intKey = (Integer) key;
if(intKey == 1 || intKey == 17){
System.out.println(Thread.currentThread().getName() + " enter for loop...");
}
}
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if(key instanceof Integer){
Integer intKey = (Integer) key;
if(intKey == 1 || intKey == 17){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " with key==" + key + " try to new node");
}
}
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null))){
if(key instanceof Integer){
Integer intKey = (Integer) key;
if(intKey == 1 || intKey == 17){
System.out.println(Thread.currentThread().getName() + " cas succeeds...");
}
}
break; // no lock when adding to empty bin
}else {
if(key instanceof Integer){
Integer intKey = (Integer) key;
if(intKey == 1 || intKey == 17){
System.out.println(Thread.currentThread().getName() + " cas fails...");
}
}
}
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
...
}
}
}
运行结果如下:
A enter for loop...
A enter for loop...
B enter for loop...
B with key==17 try to new node
B cas succeeds...
A with key==1 try to new node
A cas fails...
A enter for loop...
{17=b, 1=a}
可以看到,只有一个线程会CAS成功,另一个线程会重新进入for循环并挂在前一个节点下面。这是因为ConcurrentHashMap使用了CAS操作来保证线程安全,同时使用自旋锁来处理竞争情况。
热门推荐
开封秋游攻略:42届菊花文化节盛大开幕,这些景点免费开放!
开封一日游:人文古迹里的千年风韵
奶制品含有反式脂肪酸?事实是……
艾司唑仑:心理健康的秘密武器?
孟良崮战役:被俘虏的两万张灵甫旧部,后来去了哪里?
为啥张灵甫不到3天就全军覆没了?顾祝同说出真相:张灵甫太弱了
揭秘整编第74师覆灭:五大原因导致第一大主力折戟孟良崮
国民党十大名将战力榜单:杜聿明没进前三,排在第一的人日军最怕
元旦带娃打卡珠海长隆:超全攻略出炉!
小七孔风景区:贵州荔波的绿色宝石
小七孔古桥:教你拍出专业大片!
珠海必打卡:长隆&大剧院,你更爱哪个?
珠海夜游必打卡:日月贝灯光秀与海岸线浪漫之旅
珠海渔女守护的自然之美
反式脂肪酸又上热搜!真的一口也不能吃吗?
艾司唑仑的心理依赖风险:你了解吗?
艾司唑仑长期服用对大脑的影响有多大?
道教两大教派:正一道与全真道的差异
节能LED显示屏如何节能?
医生解答感染甲流后多久不具传染性:至少隔离至体温恢复正常的24小时后
甲流检查是验血还是咽拭子
王楚钦势不可挡,夺得WTT新加坡大满贯男单冠军,乒坛新星亮相!
魔兽世界11.0.5冰法玩法深度解析与优化指南!
张继科说国乒主力都打不过郝帅,是这样吗?
《功夫之王》揭秘:成龙李连杰的首次银幕对决
《功夫之王》:成龙的五大绝技与武术魅力
潮州古城夜市&牌坊街夜市,谁才是小吃之王?
潮州夜市美食大赏:吃货必打卡!
高速公路行车发生事故怎么办?“九字诀”一定要牢记!
清爽出行:城市短途旅行指南