MAP安全类:ConcurrentHashMap解析
下面先来说说map类下的 HashMap:
HashMap的构造方法,可以写初始化容量和加载因子,默认是16和0.75,这里可以从源码查看:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
但,HashMap在多线程下并不安全,代码如下:
package map;
import java.util.HashMap;
import java.util.UUID;
public class ConcurrentHashMapTest {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
//等价于 HashMap<String, String> map1 = new HashMap<>(16, 0.75f);
//0.75f是加载因子,16是初始化容量,最大容量是 1 << 30,即2^30
for (int i = 1; i <=100 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map.toString());
}).start();
}
}
}
这时候会出现ConcurrentModificationException错误:
Exception in thread "Thread-68" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at java.util.AbstractMap.toString(AbstractMap.java:554)
at map.ConcurrentHashMapTest.lambda$main$0(ConcurrentHashMapTest.java:14)
at java.lang.Thread.run(Thread.java:748)
这是因为hashmap在扩容加入元素期间可能被其他线程抢先遍历,则会出现这个问题:解决这个问题的方法可以使用hashtable或者用ConcurrentHashMap。
1、hashtable的解决办法是,每个方法声明关键字上加上synchronized,有点蠢,这样粒度大,花销很大。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
2、我们这里使用ConcurrentHashMap
package map;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapTest {
public static void main(String[] args) {
// HashMap<String, String> map = new HashMap<>();
// Hashtable<Object, Object> map = new Hashtable<>(); //每个方法都加锁粒度太大
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
//等价于 HashMap<String, String> map1 = new HashMap<>(16, 0.75f);
//0.75f是加载因子,16是初始化容量,最大容量是 1 << 30,即2^30
for (int i = 1; i <=100 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map.toString());
}).start();
}
}
}
执行后发现,问题解决:执行成功结果
{Thread-51=8df81, Thread-62=8ef89, Thread-50=97dcb, Thread-61=0f49c, Thread-60=6926c, Thread-55=26691, Thread-54=f0067, Thread-53=0e06f, Thread-52=182b6, Thread-48=15355, Thread-59=98a6e, Thread-58=e4571, Thread-46=3fff8, Thread-57=6675e, Thread-45=3e198, Thread-56=efe09, Thread-49=89a27}
{Thread-51=8df81, Thread-50=97dcb, Thread-71=6ad20, Thread-70=128ba, Thread-66=75374, Thread-65=5ff5c, Thread-64=e3fc4, Thread-63=7dcab, Thread-48=15355, Thread-69=a0952, Thread-46=3fff8, Thread-68=59d2e, Thread-45=3e198, Thread-67=934d8, Thread-49=89a27, Thread-62=8ef89, Thread-61=0f49c, Thread-60=6926c, Thread-55=26691, Thread-54=f0067, Thread-53=0e06f, Thread-52=182b6, Thread-59=98a6e, Thread-58=e4571, Thread-57=6675e, Thread-56=efe09}
ConCurrentHashMap的底层是:散列表+红黑树,与HashMap是一样的。
ConcurrentHashMap通过在部分加锁和利用CAS算法来实现同步。
这是put方法的源码:
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;;) {
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 (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
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;
}
下面是我粗略的分析
这是对节点的处理:
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;
}
}
}
}
链表如果大于8,则转换成树:
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
版权声明:本文为qq_42388853原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。