hashMap虽然效率好,但是多线程下是不安全的。一般在多线程的场景,有几种不同的方式去代替:
- Collections.synchronizedMap(Map)创建线程安全的map集合
- Hashtable
- ConcurrentHashMap:性能和效率好
详细解析
Collections.synchronizedMap(Map)
private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
...
HashTable
线程安全,但是效率低,因为是synchronized方法。
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* Constructs a new, empty hashtable with a default initial capacity (11)
* and load factor (0.75).
*/
public Hashtable() {
this(11, 0.75f);
}
public synchronized V put(K key, V value) {
...
}
hashtable和hashmap不同点
HashMao | HashTable | |
---|---|---|
允许key/value为空 | Yes | No(多线程环境下,无法判断null是没有元素,还是该元素的key是null) |
初始容量 | 16 | 11 |
扩容机制 | 容量翻倍 | 容量翻倍+1 |
ConcurrentHashMap
- JDK 1.8 采用 数组+链表(红黑树)
- 放弃了分段锁,采用CAS(乐观锁) + synchronized 来保证并发安全性,对每个数组元素加锁。
- 链表节点个数大于8,转化为链表
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
//采用volatile保证可见性
volatile V val;
volatile Node<K,V> next;
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
}
put
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//根据key计算计算hash值
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)
//第一次时,初始化table
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果该位置的Node还未初始化,则通过CAS插入相应的数据;
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)
//table[i]的节点的hash等于MOVED,则说明正在扩容,则帮助其扩容
tab = helpTransfer(tab, f);
else {
//tab[i]有元素,发生了碰撞
V oldVal = null;
//如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁(对链表头结点加锁)
synchronized (f) {
if (tabAt(tab, i) == f) {
//如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;
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;
}
}
}
//如果是红黑树结构,则通过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;
}
}
}
}
if (binCount != 0) {
//节点大于等于8,将链表转化为红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//插入新节点,更新元素个数
addCount(1L, binCount);
return null;
}
记录size
/**
* Base counter value, used mainly when there is no contention,
* but also as a fallback during table initialization
* races. Updated via CAS.
*/
private transient volatile long baseCount;
当插入新数据或则删除数据时,会通过addCount()方法更新baseCount。用volatile,没有额外加锁,弱一致性。
版权声明:本文为zd836614437原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。