参考资料
精妙绝伦的并发艺术品 — ConcurrentHashMap是如何保证线程安全的
一文澄清网上对 ConcurrentHashMap 的一个流传甚广的误解!
快速上手
使用方法跟普通的map没有区别,唯一区别:key和value均不能为空
if (key == null || value == null) throw new NullPointerException();
put如何保证线程安全
public V put(K key, V value) {
return putVal(key, value, false);
}
putVal
第一行:K,V的校验
if (key == null || value == null) throw new NullPointerException();
第二行:hash值的计算,在HashMap的基础上有所变化
int hash = spread(key.hashCode());
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
static final int HASH_BITS = 0x7fffffff
第三行:binCount 初始化
int binCount = 0;
第四行:for循环和部分值的初始化
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
}
第一个处理(initTable)
源码解析
for循环中第一个if,(主要作用:如果Node数组为空执行initTable)
if (tab == null || (n = tab.length) == 0)
tab = initTable();
思考1:当多个线程同时进入该方法,如何保证只有一个线程进行初始化。
private final Node<K,V>[] initTable() {
Node<K,V>[] tab;
int sc;
//在初始化动作完成之前,所有线程while循环
while ((tab = table) == null || tab.length == 0) {
//如果没有竞争sc=0,如果<0代表有其他线程修改,让出执行。
if ((sc = sizeCtl) < 0)
Thread.yield();
//CAS操作,SIZECTL是sizeCtl的偏移量,比较字段sizeCtl和局部变量sc,
//如果相同说明没有其他线程修改sizeCtl,将sizeCtl设置为-1
//由于CAS在CPU指令级别是原子的,所以只有一个线程可以进入下面这段代码。
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
//三元表达式,sc>0,n=sc,否则n=DEFAULT_CAPACITY=16
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
//Node数组初始化
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
//sc= 16-4=12
sc = n - (n >>> 2);
}
} finally {
//12
sizeCtl = sc;
}
break;
}
}
//初始化动作完成,返回tab
return tab;
}
说明:SIZECTL是sizeCtl的偏移量,Unsafe中通过offset(偏移量)来操作对象中的字段,也就是这里的sizeCtl。
总结:
- sc = sizeCtl 和 compareAndSwapInt(this, SIZECTL, sc, -1) 保证了只有一个线程可以将sizeCtl修改为-1,进而执行后续的初始化动作。(CAS)
- volatile Node<K,V>[] table 保证了Node数组table的可见性,从而避免其他线程重复初始化。(volatile)
- 执行初始化的线程在finally 代码块修改sizeCtl 的值,从而让后续线程可以正确的CAS。(数值还原),如果缺失finally会导致其他线程永远无法结束。
一点思考:
- if代码块能否换成其他语句? continue(浪费CPU时间,重复判断if)、break(跳出while,破坏语义返回null数组)、sleep(休眠时间不好指定)
demo1
public class InitTable {
/**
* 关于这里的可见性,我认为是要加的,但是我没有测出来
*/
Integer[] table;
volatile int sizeCtl;
static long SIZECTL;
static sun.misc.Unsafe U;
static Class<?> k;
static {
try {
k = InitTable.class;
U = UnsafeUtils.getUnsafe();
SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
Integer[] initTableV1() {
Integer[] tab;
int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0) {
Thread.yield();
} else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
Integer[] nt = new Integer[10];
table = tab = nt;
sc = 10;
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
}
启动类
public class InitTableClient {
public static void main(String[] args) {
InitTable initTable = new InitTable();
List<Integer[]> list = new ArrayList<>();
for (int i = 0; i < 15; i++) {
new Thread(() -> {
Integer[] integers = initTable.initTableV1();
list.add(integers);
}, String.valueOf(i)).start();
}
System.out.println("");
}
}
关于volatile table的思考,尽管我这里没有加volatile关键字,最终集合中的list都是同一个对象,没有出现重复创建的情况,后续如果知道了原因会在这里补充。
思考1:除了volatile table,还有其他操作会让线程从主存中拿到table。
第二个处理(tabAt&casTabAt)
源码解析
第二个else if,(主要作用,如果数组第i个元素为空,CAS创建一个数组节点)
//获取tab数组第(n-1)&hash处的位置的Node,如果Node为空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//cas,如果该位置没有被其他线程修改,创建一个新的Node
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;
}
tabXX源码
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
主要思想,通过tabAt方法获得Node数组指定索引处的值,如果为空,使用casTabAt来确保只有一个线程创建Node对象,也就是坑位。
demo2
public class TabAt {
Integer[] tab = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
static Unsafe U = UnsafeUtils.getUnsafe();
/**
* 数组的基础偏移量
*/
static final long ABASE;
static final long ASHIFT;
static Class<?> k = TabAt.class;
static Class<?> ak = Integer[].class;
public Integer[] getTab() {
return tab;
}
static {
ABASE = U.arrayBaseOffset(ak);
System.out.println("ABASE=" + ABASE);
int scale = U.arrayIndexScale(ak);
System.out.println("scale=" + scale);
if ((scale & (scale - 1)) != 0) {
throw new Error("data type scale not a power of two");
}
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
System.out.println("ASHIFT=" + ASHIFT);
}
/**
* 获取数组第i个元素的值
*
* @param tab
* @param i
* @return
*/
static final Integer tabAt(Integer[] tab, long i) {
return (Integer) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE);
}
/**
* cas数组第i个元素的值,如果是c变为v
*
* @param tab
* @param i
* @param c
* @param v
* @return
*/
static final boolean casTabAt(Integer[] tab, int i, Integer c, Integer v) {
return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v);
}
/**
* 将数组第i个元素修改为v
*
* @param tab
* @param i
* @param v
*/
static final void setTabAt(Integer[] tab, int i, Integer v) {
U.putObjectVolatile(tab, ((long) i << ASHIFT) + ABASE, v);
}
Integer changeVal(int index, int val) {
Integer oldVal = tabAt(tab, index);
boolean casTabAt = casTabAt(tab, index, oldVal, val);
System.out.println("casTabAt=" + casTabAt);
// setTabAt(tab, index, val);
return oldVal;
}
}
启动类
public class TabAtClient {
public static void main(String[] args) {
TabAt tab = new TabAt();
Random random = new Random();
for (int i = 0; i < tab.getTab().length; i++) {
int nextInt = random.nextInt(10);
tab.changeVal(i, nextInt);
System.out.println("nextInt:" + nextInt);
}
System.out.println(Arrays.toString(tab.getTab()));
}
}
输出
ABASE=16
scale=4
ASHIFT=2
casTabAt=true
nextInt:0
casTabAt=true
nextInt:7
casTabAt=true
nextInt:2
casTabAt=true
nextInt:8
casTabAt=true
nextInt:5
casTabAt=true
nextInt:5
casTabAt=true
nextInt:8
casTabAt=true
nextInt:7
casTabAt=true
nextInt:7
casTabAt=true
nextInt:5
[0, 7, 2, 8, 5, 5, 8, 7, 7, 5]
第三个处理
源码解析
第三个处理,fh = f.hash,== -1 时处理,f 在第二步被赋值为tabAt的结果
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
helpTransfer源码
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
//定义nextTab和sc
Node<K,V>[] nextTab; int sc;
//如果tab不为空&&f是ForwardingNode类型&&将f转为ForwardingNode后,nextTable不为空
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
//计算一个rs值
int rs = resizeStamp(tab.length);
//
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
resizeStamp源码
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
MOVED字段
static final int MOVED = -1; // hash for forwarding nodes
查找MOVED字段在类中的使用
第四个处理 else
else {
//定义oldVal
V oldVal = null;
//锁定f,f=tabAt。。。。,相当于数组的某一个元素位置,一个坑对应一把锁
synchronized (f) {
//判断数组第i个位置是否等于f
if (tabAt(tab, i) == f) {
//fh是f节点的hash
if (fh >= 0) {
//初始化为1
binCount = 1;
//沿着f链表查找
for (Node<K,V> e = f;; ++binCount) {
//定义K泛型ek
K ek;
//如果判断相等
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//oldVal = e.val
oldVal = e.val;
//如果不是没有写入
if (!onlyIfAbsent)
//写入新值
e.val = value;
//跳出循环
break;
}
//如果节点不相等
Node<K,V> pred = e;
//直到空节点的时候
if ((e = e.next) == null) {
//让这个key加入链表
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果f是树节点
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;