原子操作atomic operation是“不可中断的一个或者一系列操作”。
1、术语
缓存行:
缓存的最小操作单位。
比较并交换(Compare and Swap):
CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较旧值有没有变化,如果没有变化,才交换成新值,旧值发生了变化则不交换。
内存顺序冲突:
内存顺序冲突一般是由假共享内存引起的。假共享是指多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU里的操作无效,当出现这种内存顺序冲突时,CPU必须清空流水线。
2、处理器实现原子操作的两种方式
第一种方式:使用总线锁保证原子性
第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写操作(i++),那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后的共享变量的值会和预期的不一致。
原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行了i+1操作,然后分别写入系统内存中。
想要保证读改写操作的原子性,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量的内存地址的缓存。
处理器使用总线锁就是解决这个问题。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出次信号时,其他处理器的请求将被阻塞,那么该处理器可以独占共享内存。
第二种方式:使用缓存行保证原子性
通过缓存行锁定保证原子性,在同一时刻,需要保证对某个内存地址的操作是原子性即可。
频繁使用的内存数据会缓存在处理器的各级高速缓存里,那么原子操作就可以直接在处理器内存缓存中进行,并不需要声明总线锁。处理中使用“缓存锁定”的方式来实现复杂的原子性。
所谓缓存锁定是指内存区域如果被缓存在处理器的缓存行中,并且在Lock操作期间被锁定,那么当它执行锁操作回写到内存时,处理器不在总线上声明LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时,会使用缓存行无效。如果CPU1修改缓存行中i时使用了缓存锁定,那么CPU2就不能同时缓存i的缓存行。
有两种情况不会使用缓存锁定:
第一种是当操作的数据不能被缓存在处理器内存或者操作的数据跨多个缓存行时,则处理器会使用总线锁定。
第二种是有些处理器不支持缓存锁定。
两种方式差异:
总线锁把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁的开销比较大
3、Java实现原子操作
在Java中通过锁和循环CAS的方式来实现原子性。
第一种使用循环CAS方式实现原子性
JDK1.5的开发包中提供了AtomicInteger(用原子方式更新的int值)等。
使用CAS实现原子操作的三个问题:
1.ABA问题:可以通过AtomicStampedReference来解决ABA问题。
2.循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3.只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,可以使用循环CAS的方式来保证原子性,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性了。这是 可以使用锁。
测试代码:
public class Counter { private int count = 0; public static void main(String[] args) throws InterruptedException { countUnSafe(); countSafe(); } static void countSafe() throws InterruptedException { AtomicInteger atomicInteger = new AtomicInteger(0); CountDownLatch countDownLatch = new CountDownLatch(10); CyclicBarrier cyclicBarrier = new CyclicBarrier(10); Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { try { cyclicBarrier.await(); for(int j=0;j<100000;j++) { while(true) { int a = atomicInteger.get(); boolean flag = atomicInteger.compareAndSet(a,++a); if(flag) { break; } } } countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }); threads[i] = thread; } for(int i=0;i<threads.length;i++) { threads[i].start(); } countDownLatch.await(); System.out.println(atomicInteger.get()); } static void countUnSafe() throws InterruptedException { Counter counter = new Counter(); CountDownLatch countDownLatch = new CountDownLatch(10); CyclicBarrier cyclicBarrier = new CyclicBarrier(10); Thread[] threads = new Thread[10]; for(int i=0;i<threads.length;i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { try { cyclicBarrier.await(); for(int j=0;j<100000;j++) { counter.count++; } countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }); threads[i] = thread; } for(int i=0;i<threads.length;i++) { threads[i].start(); } countDownLatch.await(); System.out.println(counter.count); } }
}