我们都知道hashmap是线程不安全的,hashtable是线程安全的,hashtable源码是通过synchronize关键字来进行修饰的, synchronize是保证线程的同步性,是通过一种锁的协议来保证共享数据的同步的。
hashtable是锁住整个结构的,如果在多线程的竞争激烈下,hashtable的效率相对很低。
在jdk1.8以前concurrentHashMap都是通过由segment数组结构和hashentry数组结构组成的,segment是concurrentHashMap里面的可重入的锁,在concurrentHashMap里面扮演的是锁的角色,而hashEntry是用来存储键值对的数据,在concurrentHashMap包含一个segment数组,一个segment数组的结果和hashMap的结果相似,一个segment数组对应一个hashEntry数组,每个hashEntry是链表结构的元素,每个segment也守护一个hashEntry数组的元素。当对hashEntry进行修改时,必须首先获得它对应的segment锁。整体总体的就是 concurrentHashMap是由segment数组结构和hashEntry数组结构组成的,segment是concurrentHashMap的锁,hashEntry是存储键值对的数据。
concurrentHashMap
↓
segment
↓
hashEntry
在jdk1.8以后,通过node+cas+synchronize来取代segment然后来保证并发安全进行实现,只有在执行第一次put的时候才会进行node数组的初始化。如果当有修改操作时就会用到synchronize来进行锁定保证了线程的安全和使用cas来保证原子性的操作,concurrentHashMap的get方法就是根据key的hash值找到在table所对应的位置,然后再table所存储的链表或者树来进行查找相应的是否有key的节点的值。如果有则就返回对应的value,否则返回null,其实就跟hashmap很相似。
其中transient volatile Node<K,V>[] table,它其实就是一个容器数组,上面说到第一次使用put时候,插入数据就会对这个容器数组进行初始化,大小是2的幂方,这就是数组+链表(或者树)
下面说说concurrentHashMap的底层源码:↓
/**
* Creates a new, empty map with the default initial table size (16).
*/
public ConcurrentHashMap() {
}
/**
* Creates a new, empty map with an initial table size
* accommodating the specified number of elements without the need
* to dynamically resize.
*
* @param initialCapacity The implementation performs internal
* sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of
* elements is negative
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
从注释有说到Creates a new, empty map with the default initial table size (16).,从上面的 构造方法可以得出是检查参数的有效性,以及table初始化的长度,(如果在不指定的长度下默认长度是16)
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}), table
* density ({@code loadFactor}), and number of concurrently
* updating threads ({@code concurrencyLevel}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation may use this value as
* a sizing hint.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* nonpositive
*/
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
可以看到concurrencyLevel这个参数,是指能够同时更新concurrentHashMap且不产生锁竞争的最大线程数,默认值为16.
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
*
More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
通过这个可以看出原文翻译就是如果该映射里面包含了一个来自键的映射,那么久会返回这个方法的code值,否则就会返回code为null。如果指定的键为null的话,就会抛出异常,nullpointerException。
下面说说node类
/* ---------------- Nodes -------------- */
/**
* Key-value entry. This class is never exported out as a
* user-mutable Map.Entry (i.e., one supporting setValue; see
* MapEntry below), but can be used for read-only traversals used
* in bulk tasks. Subclasses of Node with a negative hash field
* are special, and contain null keys and values (but are never
* exported). Otherwise, keys and vals are never null.
* node类是指在没有提供接口的时候,支持一个setValue看到下面的MapEntry,只能够用于读遍历。
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val; 、、volatile是变量的,意思保证了可见性的,如果这个值发生了修改,那么其他线程也能够看到。
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;
}
/*
*hashmap中的node类的hashcode方法为object.hashcode方法,下面的方法其实跟hashmap的hashcode的方法是相似的
*/
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
//
public final boolean equals(Object o) {
Object k, v, u; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
/**
* Virtualized support for map.get(); overridden in subclasses.
* 在concurrentHashMap方法中新增了find的方法来辅助get的方法,在原本的hashmap中是没有find这个方法的。
*
*/
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
}
以上就是根据源码的注释以及个人的看法来讲述的,如果有什么不好的地方还请大家在下面的留言给出指导。
后续会继续更新concurrentHashMap的其他源码的个人看法和简介。