先贴一段代码,根据源码分析ConcurrentHashMap中的get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* 返回指定键映射到的值,如果此映射不包含键的映射,则返回{@code null}。
*
* <p>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.)
*
* 更正式地说,如果这个映射包含一个键{@code k}到一个值{@code v}的映射,
* 使得{@code key.equals(k)},那么这个方法返回{@code v};否则返回{@code null}。
* (最多可以有一个这样的映射。)
*
* @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;
//扩展key的Hash值
int h = spread(key.hashCode());
//如果表格不为空且表格长度大于0且所查key的所在节点不为空,(n - 1) & h相当于取模操作,即获取其索引位置。
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;
}
//如果当前节点hash值小于0,溢出的时候为负数
else if (eh < 0)
//调用find方法,返回值,或者没找到则返回空
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;
}

节点Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* ---------------- 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.
* 键值项。这个类永远不会作为用户可变的Map.Entry导出,但是可以用于批量任务中使用的只读遍历。
* 具有负哈希字段的Node的子类是特殊的,包含空键和值(但从不导出)。否则,键和val永远不会为空。
*/
static class Node<K,V> implements Entry<K,V> {
//哈希码
final int hash;
//键
final K key;
//值
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;
}

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; Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (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.
* 对map.get()的虚拟化支持;在子类覆盖。
* h:待查找key的hash
* k:待查找key
*/
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中的get方法是没有锁的。
分析一下get方法是怎么实现的多线程下数据可见性。

  1. 通过tabAt的volatile读
  2. 通过结点Node的volatile属性next
  3. 通过结点Node的volatile属性val

JVM保证了volatile修饰的数据的内存可见性,后期专门学习一下volatile关键字。