Java中的原子操作
文章目录
- 1、什么是原子操作
- 2、Java中原子操作的实现方式
- 2.1 用CAS实现原子操作
- 2.1.2 使用CAS实现原子操作
- 2.1.3 CAS实现原子操作的问题
- 3、Java中使用锁实现原子操作
- 4、CPU如何实现原子操作
1、什么是原子操作
原子操作:一个或多个操作在CPU执行过程中不被中断的特性
当我们说原子操作时,需要分清楚针对的是CPU指令级别还是高级语言级别。
比如:经典的银行转账场景,是语言级别的原子操作;
而当我们说volatile修饰的变量的复合操作,其原子性不能被保证,指的是CPU指令级别。
二者的本质是一致的。
“原子操作”的实质其实并不是指“不可分割”,这只是外在表现,本质在于多个资源之间有一致性的要求,操作的中间态对外不可见。
比如:在32位机器上写64位的long变量有中间状态(只写了64位中的32位);银行转账操作中也有中间状态(A向B转账,A扣钱了,B还没来得及加钱)
2、Java中原子操作的实现方式
Java使用锁和自旋CAS实现原子操作
2.1 用CAS实现原子操作
2.1.2 使用CAS实现原子操作
public class Counter {private final AtomicInteger atomicI = new AtomicInteger(0);private int i = 0;public static void main(String[] args) {Counter counter = new Counter();ArrayList<Thread> list = new ArrayList<>(1000);long start = System.currentTimeMillis();IntStream.range(0, 100).forEach(u -> {list.add(new Thread(() ->IntStream.range(0, 1000).forEach(v -> {counter.safeCount();counter.count();})));});list.forEach(Thread::start);/* wait for all the threads to complete*/list.forEach(u -> {try {u.join();} catch (InterruptedException e) {e.printStackTrace();}});System.out.println(counter.i);System.out.println(counter.atomicI.get());System.out.println(System.currentTimeMillis() - start);}/* 使用CAS 来实现原子操作*/public void safeCount() {for (; ; ) {int i = atomicI.get();/*Atomically sets the value to the given updated value if the current value == the expected value.*//*Parameters:expect - the expected valueupdate - the new value*//* 其实,假如使用 原子类来实现计数器,不需要直接用 cas 的API,原子类已经提供了现成的API了*/boolean success = atomicI.compareAndSet(i, i + 1);if (success) {break;}}}/* 使用 锁 来实现原子操作*/public synchronized void safeCount1() {i++;}/* 线程不安全的累加*/public void count() {i++;}}
并发包中提供了很多原子类来支持原子操作:
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference
- LongAdder
2.1.3 CAS实现原子操作的问题
CAS是并发包的基石,但用CAS有三个问题:
-
1)ABA问题
根源:CAS的本质是对变量的current value,期望值expected value进行比较,二者相等时,再将 给定值given update value设为当前值。因此会存在一种场景,变量值原来是A,变成了B,又变成了A,使用CAS检查时会发现值并未变化,实际上是变化了。
对于数值类型的变量,比如int,这种问题关系不大,但对于引用类型,则会产生很大影响。ABA问题解决思路:版本号。在变量前加版本号,每次变量更新时将版本号加1,A -> B -> A,就变成 1A -> 2B -> 3A。
JDK5之后Atomic包中提供了AtomicStampedReference#compareAndSet来解决ABA问题。public boolean compareAndSet(@Nullable V expectedReference,V newReference,int expectedStamp,int newStamp) Atomically sets the value of both the reference and stamp to the given update values if the current reference is == to the expected reference and the current stamp is equal to the expected stamp. Parameters: expectedReference - the expected value of the reference newReference - the new value for the reference expectedStamp - the expected value of the stamp newStamp - the new value for the stamp -
2)循环时间长则开销大
自旋CAS若长时间不成功,会对CPU造成较大开销。不过有的JVM可支持CPU的pause指令的话,效率可有一定提升。pause作用:
- 延迟流水线指令(de-pipeline),使CPU不至于消耗过多执行资源。
- 可避免退出循环时因内存顺序冲突(
memorey order violation)引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
-
3)只能保证一个共享变量的原子操作
CAS只能对单个共享变量如是操作,对多个共享变量操作时则无法保证原子性,此时可以用锁。另外,也可“取巧”,将多个共享变量合成一个共享变量来操作。比如a=2,b=t,合并起来ab=2t,然后用CAS操作ab.
JDK5提供
AtomicReference保证引用对象间的原子性,它可将多个变量放在一个对象中来进行CAS操作。
3、Java中使用锁实现原子操作
锁机制保证只有拿到锁的线程才能操作锁定的内存区域。
JVM内部实现了多种锁,偏向锁、轻量锁、互斥锁。不过轻量锁、互斥锁(即不包括偏向锁),实现锁时还是使用了CAS,即:一个线程进入同步代码时用自CAS拿锁,退出块的时候用CAS释放锁。
synchronized锁定的临界区代码对共享变量的操作是原子操作。
4、CPU如何实现原子操作
首先,CPU会自动保证基本的内存操作的原子性。CPU保证从内存中读写一个字节是原子的,即:当一个CPU读一个字节时,其他处理器不能访问这个字节的内存地址。
但对于复杂的内存操作如跨总线跨度、跨多个缓存行的访问,CPU是不能自动保证的。不过,CPU提供总线锁定和缓存锁定。
- 1、使用总线锁保证原子性
假如多个处理器同时读改写共享变量,这种操作(e.g. i++)不是原子的,操作完的共享变量的值会和期望的不一致。
原因:多个处理器同时从各自缓存读i,分别 + 1,分别写入内存。要想保证读改写共享变量的原子性,必须保证CPU1读改写该变量时,CPU2不能操作缓存了该变量内存地址的缓存。
总线锁就是解决此问题的。
总线锁:利用LOCK#信号,当一个CPU在总线上输出此信号,其他CPU的请求会被阻塞,则该CPU可以独占共享内存。
- 2、使用缓存锁保证原子性
同一时刻,其实只要保证对某个内存地址的操作是原子的即可,但总线锁定把CPU和内存间的通信锁住了。锁定期间,其他CPU不能操作其他内存地址的数据,所以总线锁定的开销比较大。目前CPU会在一些场景下使用缓存锁替代总线锁来优化。
频繁使用的内存会被缓存到L1、L2、L3高速cache中,原子操作可直接在高速cache中进行,不需要声明总线锁。
缓存锁是指:缓存一致性机制阻止同时修改由两个以上CPU缓存的内存区域数据,当其他CPU回写已被锁定的缓存行数据时,会使缓存行无效。
看下图:i 是同时被CPU1和CPU2缓存的内存区域变量;CPU1 修改缓存行中 i 时使用缓存锁定,则CPU2 不能同时缓存 i 的缓存行。(i 的缓存行会失效)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TMdvKCO2-1618653147136)(http://note.youdao.com/yws/res/36381/WEBRESOURCE8d20b5f4eb06205db479ed38b76d5592)]](https://img-blog.csdnimg.cn/20210417175954128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMwMTE4NTYz,size_16,color_FFFFFF,t_70)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
