JUC 源码分析之-CAS
CompareAndSwap
乍看中文含义是:比较和交换。实现的逻辑就是先比较目标值,如果是期望值则更新,否则返回false。那么这个操作怎么保证原子性呢?具体交给各操作系统底层去实现,jvm会根据不同的操作系统编写不同的c++代码实现,java的api中凡事标注native的方法都是调用底层c++方法
java中CAS的调用
java提供了一个访问入口类:sun.misc.Unsafe
如果我想模仿大师Doug Lea一样写些底层操作的代码。可以直接使用Unsafe 类吗?答案是可以,但官方不建议。Unsafe类是不能直接创建的,也不提供静态工厂方法(其实提供了getUnsafe(),但只给自己类加载器家装的类使用)。那怎么办? Oracle没有把路堵死,利用反射方法就可以拿到theUnsafe成员属性,也就是一个Unsafe实例,这样我们就能直接调用它里面的很多有用的方法。直接上代码:
public class Test {
public static void main(String[] args) throws NoSuchFieldException,
SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");//反射得到theUnsafe对应的Field对象
field.setAccessible(true);//获取Field访问权限
Unsafe unsafe = (Unsafe) field.get(null);//传入null是因为该Field为static的
System.out.println(unsafe);
}
}
Unsafe常用方法
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws NoSuchFieldException,
SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
System.out.println(unsafe);
//通过Unsafe类实例化的对象,不会调用默认构造方法
Student student1 = (Student) unsafe.allocateInstance(Student.class);
System.out.println(student1);
//正常调用
Student Student2 = new Student();
System.out.println(Student2);
}
}
class Student {
private String name = "";
private int age = 0;
public Student() {
this.name = "zangsan";
this.age = 18;
}
@Override
public String toString() {
return name + ": " + age;
}
}
输出结果:
sun.misc.Unsafe@776ec8df
null: 0
zangsan: 18
我们看到通过allocateInstance操作生成的对象,输出:null: 0
long nameOffeSet=unsafe.objectFieldOffset(Student.class.getDeclaredField("name"));
System.out.println(nameOffeSet);
输出:
16
-
compareAndSwapInt、compareAndSwapObject、compareAndSwapLong
以compareAndSwap开头的方法还有很多,JUC里面大量调用类此类方法判断线程能否原子得更新某个属性。compareAndSwapInt(Object var1, long var2, int var4, int var5)
var1:要操作的对象实例
var2:属性偏移量,就是根据objectFieldOffset算出来的long值
var4:期望值
var5:更新的目标值
Student Student2 = new Student();
System.out.println(Student2);
//将Student2 的age由18 改为28
long ageOffeSet=unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
unsafe.compareAndSwapInt(Student2,ageOffeSet,18,28);
System.out.println(Student2);
zangsan: 18
zangsan: 28
可以看到我们拿到age属性的偏移量后,就能直接将原值18更新为28.
- getObjectVolatile
类似的getIntVolatile、getBooleanVolatile、getByteVolatile 等等都是在多线程环境下,保证能拿到实时更新的数据。jmm的volatile语义实现。具体就是通过插入内存屏障指令,禁止处理器冲排序和处理器的缓存锁实现来保证。
System.out.println(unsafe.getIntVolatile(Student2,ageOffeSet));
28
ConcurrentHashMap 中使用CAS
- 原子获取和设置Node节点:
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);
}
- Map初始化时,原子更新Node容量属性:
U.compareAndSwapInt(this, SIZECTL, sc, -1)
sizeCtl =-1 表示当前线程竞争成功,只能当前线程能执行初始化操作,其他线程只能自旋等待( Thread.yield(); // lost initialization race; just spin)
- Map在新增数据(put操作)后原子更新数据总量
注意:Map的总量是BASECOUNT加上CounterCell[] 数组中存放的数据量的总和。
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}