1.案例
在 多线程 中使用 HashMap 时,会有 线程安全 的问题,下面的案例中使用1000个 线程 向同一个HashMap中分别插入10条数据时,就会出现线程安全问题
package org.poiuy.java.juc.concurrenthashmap;
import java.util. hash Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
/**
* ConcurrentHash put方法
*
* HashMap put方法分析,线程安全问题
* ConcurrentHashMap put方法分析
*/public class ConcurrentHashMapPutApplication {
private static final int THREAD_NUM = 1000;
public static void main(String[] args) {
hashmapMultiPut();
concurrentHashmapMultiPut();
}
/**
* 使用ConcurrentHashMap代替HashMap
*/ private static void concurrentHashmapMultiPut() {
try {
//使用ConcurrentHashMap代替HashMap
ConcurrentHashMap map = new ConcurrentHashMap();
CountDownLatch latch = new CountDownLatch(THREAD_NUM);
//模拟10000个线程向ConcurrentHashMap中同时添加10个数据
for(int i = 0;i < THREAD_NUM;i++){
new Thread(() -> {
for(int j = 0;j < 10;j++){
map.put(Thread.currentThread().getId() + "." + j,Thread.currentThread().getId() + " : " + j);
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("ConcurrentHashMap count :" + map.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* HashMap的put多线程问题
*/ private static void hashmapMultiPut() {
try {
//HashMap的put多线程问题
HashMap map = new HashMap();
CountDownLatch latch = new CountDownLatch(THREAD_NUM);
//模拟10000个线程向HashMap中同时添加10个数据
for(int i = 0;i < THREAD_NUM;i++){
new Thread(() -> {
for(int j = 0;j < 10;j++){
map.put(Thread.currentThread().getId() + "." + j,Thread.currentThread().getId() + " : " + j);
}
latch.countDown();
}).start();
}
latch.await();
System.out.println("HashMap count :" + map.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} |
从输出的结果我们可以看到HashMap最后的总数总是小于10000,当我们使用ConcurrentHashMap时就不会出现问题
2.HashMap的线程安全问题
HashMap的put方法中,如果在多线程环境下,很对地方都会有线程安全的问题
比如631行,判断数组中没有元素时,直接创建新的节点放入到数组下标中
可以看出以上以及更多的地方都不是原子操作,将会带来安全性问题
为了解决安全性问题,我们可以使用加锁的方式,比如使用Hashtable来替代HashMap,但是这个方式性能不是很高
多线程环境下推荐使用ConcurrentHashMap
3.ConcurrentHashMap的put方法
ConcurrentHashMap的put方法中为了解决线程安全问题,使用了CAS和锁的方式来处理
CAS是一种乐观锁的实现,它在操作是将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值,整个过程是原子操作
另外加锁操作时,也只是对数组中要操作的单个下标进行加锁,不影响其他下标下的元素的操作
final V putVal(K key, V value, boolean onlyIfAbsent) {
//计算hash值
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh; K fk; V fv;
//集合为空,进行初始化操作
//此处会有安全问题,在initTable方法中处理
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//如果插入的数据在数组指定下标下的节点为空,则直接将新的数据添加到数组下标下
//此处判断是使用Unsafe的getReferenceAcquire方法进行判断,该方法是原子性的
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//使用Unsafe的compareAndSetReference方法设置值,方法是原子性的
//如果数组下标下为空,则创建新节点并添加到数组中
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break ;
}
//如果该节点正在执行扩容移动操作,则调用helpTransfer方法帮助扩容移动
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//key值时数组下标中对应的元素,则进行值修改操作
//如果onlyIfAbsent参数为true,,数组下标中的元素的key值存在且value不为空,则不进行修改动作,直接退出
else if (onlyIfAbsent && fh == hash && ((fk = f.key) == key || (fk != null && key.equals(fk))) && (fv = f.val) != null)
return fv;
//其他情况
//1.节点是数组下标中的元素,且可以进行修改
//2.节点不存在,且对应的数组下标已经存在元素
else {
V oldVal = null;
//对数组下标中元素f加锁,后续操作是线程安全的
synchronized (f) {
//进一步判断,防止其他线程修改或删除f节点
if (tabAt(tab, i) == f) {
// 链表 结构处理
if (fh >= 0) {
binCount = 1;
//遍历链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
//修改value逻辑
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//添加luoji,添加到链表尾部
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value);
break;
}
}
}
// 红黑树 处理,调用putTreeVal方法
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;
}
}
//ReservationNode抛出异常
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
//是否要转变成为红黑树
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//扩容方法
addCount(1L, binCount);
return null;
} |
文章来源:智云一二三科技
文章标题:ConcurrentHashMap源码分析 – put方法
文章地址:https://www.zhihuclub.com/201293.shtml