JUC(三)CAS与原子变量

一、CAS

1、CAS简介
  • CAS(compare and swap),比较并交换。
  • CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。
  • 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
  • 通过CAS可以实现原子操作
2、ABA问题
  • 如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际变化了。
  • ABA问题可以通过添加版本号来解决:1A-2B-3A。
3、乐观锁、悲观锁
  • 乐观锁认为每次修改数据时不会有人同时修改数据。执行更新时判断在此期间是否有人修改了数据,如果数据发生变动则放弃此次更新,否则执行更新。
  • 悲观锁认为每次修改数据时都会有人同时修改数据。执行更新时锁住数据,不允许其他人更新。
  • CAS属于乐观锁,Synchronized属于悲观锁。
4、CAS效率

CAS效率

从图中可以看出:
在单核系统中,CAS省去了锁升级、抢锁等过程;
在多核系统中,传统加锁会导致只有一条线程在执行任务,而CAS则可以多线程同时执行任务,提升效率。

注意:如果线程竞争比较大,由于其他线程更新失败,CAS会一直重试,导致CPU一直执行重复任务,浪费CPU资源,此时使用锁可能是个更好的选择。

二、原子变量

基于CAS操作,Java提供了一套API,在java.util.concurrent.atomic包下。常用的如:AtomicInteger、AtomicLong、AtomicBoolean等。原子变量是线程安全的。

AtomicInteger atomicInteger = new AtomicInteger(1);
// 以AtomicInteger为例,i++可以写成如下代码
atomicInteger.incrementAndGet();

分析一下AtomicInteger源码:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    // volatile修饰
    private volatile int value;

    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }
}

可以看到:
1、AtomicInteger中的value被volatile修饰,保证了可见性;
2、incrementAndGet通过Unsafe.getAndAddInt实现。

public class Unsafe {
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            //重新获取var5的值
            var5 = this.getIntVolatile(var1, var2);
        // 如果CAS更新失败,重复do中的代码
        } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
    }

    // expected:期望值,update:目标值
    public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
}

再看Unsafe的源码,getAndAddInt中,会通过CAS操作尝试更新,若更新失败,则会一直重试。

三、原子累加器

为了对多线程下大量自增操作进行优化,Java还提供了原子累加器:LongAdder。

原子累加器原理

伪共享问题

CPU为了效率,每次会将内存中的数据读取到高速缓冲区,高速缓冲区中的最小缓存单位是Cache Line,一般是64byte,一个long类型是8byte,因此一个缓存行可以存储8个long类型的变量。

于是可能出现如下问题:
1、我们想core1处理cell[0]的数据,core2处理cell[1]的数据;
2、由于读取了一行数据,导致core1、core2同时有cell[0]、cell[1]的数据;
3、因此当core1更新了cell[0]时,core2也需要从内存中重新读取数据,来保证数据的一致性。

当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享

解决方法: java8提供@sun.misc.Contended注解

// LongAdder中的Cell便是使用@sun.misc.Contended注解来解决伪共享问题
@sun.misc.Contended static final class Cell {}

伪共享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值