Compare And Set 比较并设置
synchronized存在的问题:
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
一个线程持有锁会导致其他需要此锁的线程挂起
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险
为此CAS乐观锁概念应运而生
注意,乐观锁其实已经没有锁的概念,而是假设没有冲突的去完成某项操作,如果因为冲突失败就重试,直到成功为止。
CAS思想
内存值V,旧的预期值A,要修改的新值B
当且仅当预期值A和内存值V相同时将内存值修改为B。
这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,(之后再进行自旋,直至成功。)否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存
乐观锁有下面的缺点:
只能保证一个共享变量的原子操作
长时间自旋可能导致开销大
ABA问题。CAS的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。解决的思路是引入版本号,每次变量更新都把版本号加一。
Jdk中相关原子操作类的使用
更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
`
AtomicStampedReference:按版本号进行区分的原子类
//入参分别是值和版本号
AtomicStampedReference<String> asr =
new AtomicStampedReference<>("Mark",0);
asr.getReference()//获取值
asr.getStamp();//获取版本号
//入参分别是,原来的值,期待值,原来的版本号,新的版本号
//当cas检测已被其他线程改过时会返回false
asr.compareAndSet(oldReferenc, oldReferenc+"Java",
oldStamp, oldStamp+1));
AtomicIntegerArray一些包装原子类的使用,修改的是包装对象的值
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0, 3);
System.out.println(ai.get(0));//输出3
System.out.println(value[0]);//输出1
}