CPU原子操作实现方式
cpu一般用锁总线或锁缓存的方式实现原子操作。
首先每个cpu可以保证从系统中读取或写入一个字节是原子的。当一个cpu访问内存中一个字节时其他cpu不能访问这个字节。P6以及最新的处理器同一个缓存行里16/32/64位的操作是原子的,但是对于跨总线跨缓存行的数据无法保证访问的原子性,这种数据就需要用总线锁或者缓存锁来实现。
总线锁:所谓总线锁就是使用处理器提供的一个Lock#信号 当一个处理器在总线上输出这个信号其他处理器的请求会被阻塞,即只有这个cpu可以访问共享内存。
缓存锁:总线锁的方式虽然可以实现原子性但是由于阻塞总线,开销比较大,所以现在的处理器在某些场合下会使用缓存锁来替代。频繁使用的内存会被缓存在cpu的高速缓存中,P6和目前的处理器可以使用缓存锁定的方式,实现复杂的原子性。
缓存锁定:如果内存区域在cpu的缓存行中,并且使用了Lock 指令,那么在操作完成时就将结果直接回写到内存,并且不用在总线上声明Lock信号来锁总线。CPU的缓存一致性机制会保证同一时间只有一个cpu更改同一块主内存数据,并且修改完主内存数据后其他cpu的缓存的该数据会失效。这样就避免了锁总线的开销。
两种情况下不能使用缓存锁定:1)有的CPU不支持缓存锁定 2)操作的内容不在CPU缓存中。
java原子操作实现方式
java 中实现原子操作的方式使 锁 和 CAS操作。
CAS操作:JVM的CAS操作利用了 CPU的CMPXCHG指令。通过循环自旋来实现原子性操作。代码示例如下:
AtomicInteger i=0;
for(int i=0;i<10;i++){
for(;;){
int ii=i.get();
boolean succ = i.compareAndSet(ii,++ii);
if(succ)//如果没有修改值成功就重新获取值再次修改不断循环 直到成功为止
break;
}
}
CAS问题:
1)ABA问题:cas只是将比较原来的值和传入的期望值是否相同,如果相同则可以修改,如果一个变量由A变为了B然后又编程了A 这个时候我们检查时会认为值没有变,但其实是变了。(感觉这种影响应该不大,毕竟值一样了就不会影响准确性)。解决办法:变量中同时维护一个版本号 每次更新版本号都加1,1A 2B 3A 。
2)自旋消耗CPU,长时间空转会十分消耗CPU 可以设置时间限制
3)只能保证一个共享变量的原子操作。解决 可以尝试将 两个变量组合成一个比如 i=1 j=4 合并成 ij=14
锁机制
java 锁机制保证只有获得了锁的线程才可以操作指定内存,保证了原子性。