程老师原文地址:http://flychao88.iteye.com/blog/2269438
原文如下:
一、CAS简介
CAS:Compare and Swap, 翻译成比较并交换。
java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁,使用这些类在多核CPU的机器上会有比较好的性能.
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
今天我们主要是针对AtomicInteger的incrementAndGet做深入分析。
二、JAVA实现部分
Java代码
循环的内容是
1.取得当前值
2.计算+1后的值
3.如果当前值没有被覆盖的话设置那个+1后的值
4.如果设置没成功, 再从1开始
在这个方法中可以看到compareAndSet这个方法,我们进入看一下。
Java代码
调用UnSafe这个类的compareAndSwapInt
Java代码
JAVA程序也就跟踪到这里为止了,剩下的就是通过JNI调用C程序了,可是我奇怪的是为什么变量名都是var1,var2这样的命名呢?JAVA编程规范不是说不使用1,2等没有含义的字符命名吗?
三、JNI原生实现部分
在openJDK中找到找到unsafe.cpp这个文件,代码如下:
Java代码
核心方法是compxchg,这个方法所属的类文件是在OS_CPU目录下面,由此可以看出这个类是和CPU操作有关,进入代码如下:
Java代码
这个方法里面都是汇编指命,看到LOCK_IF_MP也有锁指令实现的原子操作,其实CAS也算是有锁操作,只不过是由CPU来触发,比synchronized性能好的多。
**********************学习笔记开始*********************************
又开始深入学习了,大神就是这么厉害。
写个demo测试下吧。跟大神一样就演示下AtomicInteger
public class CASTest {
static int i = 0;
static AtomicInteger j = new AtomicInteger(0);
public void count() {
i++;
}
public void safecount() {
j.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException { // TODO
final CASTest c = new CASTest();
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (int k = 0; k < 100; k++) {
executorService.execute(new Runnable() {
@Override
public void run() {
for (int m = 0; m < 100; m++) {
c.count();
c.safecount();
}
}
});
}
Thread.sleep(5000);
System.out.println("castest 50 threads run add:");
System.out.println("count:" + i);
System.out.println("safecount:" + j.get());
}
}
运行结果如下(每次结果可能不同):
可以看出并发情况下,不采用线程安全的类容易出错。当然这背后是基于cas的,上文已经介绍了。
知识点1多核cpu如何去实现“原子操作”。相关知识点:缓存行(cacheline)、CPU流水线(CPU line)
处理器保证系统从内存当中读取一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器是不能访问这个字节的地址的。最新的Intel X86能保证单处理器对同一缓存行里进行的16/32/64位操作是原子的。复杂的内存操作如跨总线宽度、跨缓存行,处理器通过总线锁定和缓存锁定来保证原子性。这两种机制Intel提供很多lock指令来实现,比如上文说的cmpxchg。
知识点2:JDK文档说cas同时具有volatile读和volatile写的内存语义。
针对上文说的cmpxchg指令,在多处理器下会加入LOCK前缀(LOCK cmpxchg),单处理器会忽略LOCK前缀。
Intel对lock前缀有特殊说明:
1.根据内存区域不同提供总线锁定和缓存锁定。
2.禁止改指令与之前和之后的读指令和写指令重排序。
3.把写缓冲区的数据全部刷新到内存中。
2,、3点所具有的内存屏障效果,满足了volatile读和volatile写的内存语义。
知识点3:CAS缺点:
问题1:ABA问题
再补充下AtomicStampedReference
public class AtomicStampedReference {
private static class Pair {
final T reference;//维护对象引用
final int stamp;//标识版本
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static Pair of(T reference, int stamp) {
return new Pair(reference, stamp);
}
}
...
public boolean compareAndSet(V expectedReference, //更新之前的原始值
V newReference,//要更新de值
int expectedStamp,//期待的版本
int newStamp) {//要更新的版本
Pair current = pair;
return
expectedReference == current.reference &&//判断相等,说明值未变化
expectedStamp == current.stamp &&//版本想等
((newReference == current.reference &&
newStamp == current.stamp) ||//要更新的值、版本与老的相等
casPair(current, Pair.of(newReference, newStamp)));//cas更新pair
}
参考:http://ifeve.com/atomic-operation/