在 atomic 包中, 大多数类都是借助 unsafe 类来实现的, 如以下代码
- public static AtomicInteger count = new AtomicInteger(0);
- private static void add() {
- count.incrementAndGet();
- }
incrementAndGet()方法的实现如下:
- public final int incrementAndGet() {
- return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
- }
我们再继续深入 getAndInt()方法, 实现如下:
- public final int getAndAddInt(Object var1, long var2, int var4) {
- int var5;
- do {
- var5 = this.getIntVolatile(var1, var2);
- } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
- return var5;
- }
在以上代码中我们着重要说的是 compareAndSwapInt(var1, var2, var5, var5 + var4) 这个方法, compareAndSwap, 取每个单词首写字母, 就是我们经常说的 cas. 这个方法中有四个参数 var1 为当前对象, 即代码中的 count,var2 为当前值, 如想计算 2 加上 1 等于 3 的操作, var2 即为 2,var4 为增加量, 也就是这个例子中的 1,var5 为调用底层方法得到的底层当前的值, 如果没有其他线程改变底层当前值, 返回为 2,compareAndSwapInt 方法的作用为, 如果 var2(当前值)与 var5(底层当前值相等)则将底层值覆盖为底层当前值 (var5)+ 增加量(var4), 否则(其他线程更改了底层当前值, var5 不等于 var2), 重新从底层方法取一次 var5 的值, 如此时 var5=4, 并重新从 var1(当前对象) 取一次 var2 的值, 如此时 var2 的值也变为 4, 则采取相加操作覆盖底层值, 如果 var2 与 var5 仍不等, 则继续循环取值, 直到相等为止.
总结一下, CAS 操作包含三个操作数, 内存位置 (V), 预期原值(A) 和新值(B), 如果内存位置的值与预期原值相匹配, 处理器会自动将该位置值更新为新值, 否则处理器不做任何操作. CAS 指令一般都返回该位置的值(有特殊情况只返回是否成功),CAS 简而言之就是, 我认为位置 V 应该包含值 A, 如果包含该值, 就将 B 放到这个位置, 否则不更改该位置的值, 只告诉我这个位置现在的值即可 .
对于 getIntVolatile 方法和 compareAndSwapInt 的实现如下:
- public native int getIntVolatile(Object var1, long var2);
- public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可将两个方法都由 native 修饰, native 修饰的方法为底层方法, 一般由 c 语言来实现.
锁分为悲观锁和乐观锁, 独占锁是一种悲观锁, synchronized 是一种独占锁, 如果锁被占用, 其他需要锁的线程就会被挂起, 直到持有锁的线程释放锁, 它会产生的问题如下:
a, 在多线程竞争下, 加锁释放锁会导致比较多的上下文切换和调度延时, 引起性能问题
b, 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置, 引起性能风险
另一种就是乐观锁, 它每次执行时并不加锁而是假设没有冲突的去完成操作, 如果因为冲突失败就重试, 直到成功为止, 而客观锁用到的机制就是 CAS. 虽然 CAS 可以很高效的解决原子操作, 但是 CAS 仍然存在三大问题:
a,ABA 问题, 如果一个值原来是 A, 后被改成 B, 之后又改为 A, 那么使用 CAS 进行检查时发现它的值并没有发生变化, 但是实际上却发生变化了, ABA 问题的解决思路就是使用版本号, 在变量面前加上版本号, 每次变量更新的时候把版本加一, 上面博客中我们提到 atomic 包中有专门的类来解决 ABA 问题
b, 循环时间长开销大. CAS 循环如果长时间不成功, 会给 CPU 带来非常大的执行开销. 如果 JVM 能支持处理器提供的 pause 指令则效率会有一定的提升, pause 指令有两个作用, 推迟流水线执行, 使 cpu 不会消耗过多的执行资源和避免退出循环的时候因内存顺序冲突而引起 CPU 流水线被清空, 提高 CPU 执行效率
c, 只能保证一个共享变量的原子操作. 当对一个共享变量执行操作时, 我们可以使用循环 CAS 来保证原子操作, 但是对多个共享变量操作时, 循环 CAS 就无法保证操作的原子性, 这个时候可以用锁或把多个共享变量合并成一个共享变量来操作或者把多个变量放在一个对象里 (atomic 包中有对引用类型的原子操作) 来进行 CAS 操作
来源: https://www.cnblogs.com/sbrn/p/8999709.html