1、java原子类
在java.util.concurrent.atomic下有一些原子操作类,例如:
java.util.concurrent.atomic.AtomicBoolean;
java.util.concurrent.atomic.AtomicInteger;
java.util.concurrent.atomic.AtomicIntegerArray;
java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
有一些类似的方法,比如:
-
AtomicBoolean有 public final boolean compareAndSet(boolean expect, boolean update) ;
-
AtomicInteger有 public final boolean compareAndSet(int expect, int update) ;
等等,基本上都有这么一个compareAndSet方法。
compareAndSet主要做了三件事
- 比较expect是否与当前数据相等
- 如果相等,把当前数据更新为update
- 最重要的是,这两步是原子操作,在多线程是安全的
- 在很多场景中使用这些原子操作,可以设计一些线程安全操作,例如 使用AtomicBoolean优雅初始化资源
2、CPU CAS指令
通过查看AtomicBoolean的源代码发现,调用了Unsafe JNI代码:
package java.util.concurrent.atomic;
import sun.misc.Unsafe;
public class AtomicBoolean implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
...
}
- 而unsafe.compareAndSwapInt(this, valueOffset, e, u),这段代码本质则是调用了CPU的CAS底层指令,see here
- 通过CAS指令,在加上一些非阻塞算法,就可以写出多线程安全的乐观锁。关于非阻塞算法
3、CAS一些问题
CAS虽然很高效的解决原子操作,但是CAS仍然存在一些问题。ABA问题,循环时间长开销大。
1.ABA问题。
- 因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新。
- 但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
- ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
- 从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。关于ABA问题参考文档
2. 循环时间长开销大。
- 如果CAS如果长时间不成功,会给CPU带来非常大的执行开销。
- 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。