原子操作
原子操作
原子操作就是“不可被中断的一个或一些列操作”。在并发编程中,原子操作可以说是最常见的一个术语。
处理器如何实现原子操作
首先处理器会自动保证基本的内存操作的原子性,处理器保证从系统内存中读取或者写入一个字节是原子的,意思就是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。
1、使用 总线锁保证原子性
如果多个处理器同时对共享变量进行读后写操作(自增操作i++就是典型的读后写操作),那么共享变量就会被多个处理器同时进行操作,这样读后写操作就不是原子性的了,操作完之后共享变量的值会和期望的不一致。例如,初始化共享变量i=1,我们进行两次i++操作,我们期望的结果是3,但是可能结果是2。
所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器需要加锁操作共享内存时,该处理器在总线上输出此信号,那么其他处理器的请求将被阻塞住,那么该处理器就可以独占共享内存。
2、使用缓存锁定保证原子性
在同一时刻,我们只需要保证对某个内存地址的操作是原子性的即可,但总线锁把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销非常大,目前处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。
所谓缓存锁定是指,在最新的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”,缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据。一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
Java如何实现原子操作
1、使用循环CAS实现原子操作
所谓CAS,简单说来就是,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B。这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;类似数据库中乐观锁的版本控制机制。
JVM中的CAS操作是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。
public class CASCounting {
private static AtomicInteger atomicI=new AtomicInteger(0);
private static int i=0;
private static int si=0;
//用CAS保证自增操作原子性
private static void safeCount(){
for(;;){
int current=atomicI.get();
int next=current+1;
boolean suc=atomicI.compareAndSet(current, next);
if(suc){
break;
}
}
}
//非原子性操作
private static void unsafeCount(){
i++;
}
//使用加锁同步保证原子性
private synchronized static void synCount(){
si++;
}
public static void main(String[] args){
int count=1000;
Thread[] threads=new Thread[count];
long start=System.currentTimeMillis();
for(int i=0;i<count;i++){
threads[i]=new Thread(new Runnable(){
@Override
public void run(){
for(int i=0;i<1000;i++){
//safeCount();
//unsafeCount();
synCount();
}
}
});
}
for(int i=0;i<count;i++){
threads[i].start();
}
while(Thread.activeCount()>1){
Thread.yield();
}
//System.out.println(atomicI.get());
//System.out.println(i);
System.out.println(si);
System.out.println(System.currentTimeMillis()-start);
}
}
输出结果分别是:978018——93;1000000——110;1000000——218;
- 点赞
- 收藏
- 关注作者
评论(0)