Java中的锁(5):乐观锁的时间机制---CAS实现原理

本文深入讲解了CAS(CompareAndSwap)指令的原理及其在Java中的应用。探讨了CAS的原子性和重试策略,并通过AtomicInteger类的具体实现展示了如何利用Unsafe类实现高效的无锁操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

乐观锁思想实现原理是一种无锁原理。CAS指令是实现无锁原理的方案之一。

1、CAS是什么?

CAS的全称是Compare And Swap,即比较交换。 

其核心思想: CAS(V,E,N);   V表示要更新的变量的内存位置,E表示更新变量的预期原值,N表示更新变量的更新值。

在当前线程中,如果要更新变量预期原值E等于当前内存位置中的值,就把更新该内存位置的值,更新后的值为N。如果不相等,说明当前内存位置的值被其他线程更新,当前线程就不再做任何操作。所以,每一次CAS操作时,线程会先去读一下当前值,然后进行一次CAS操作:把期望值(刚才读出来的数据)跟现在内存位置的数据作一个比较,如果比较成功就可以把新的值写入。


2、关于CAS的两个问题

问题1:CAS操作对比期望值与实际值失败后就重试的策略,会不会很浪费资源?

关于这个问题,CAS是抱着乐观的态度进行操作的。它认为自己有非常在的概率是能够成功完成当前操作的,所以在CAS看来,完不成便重试是一个小概率事件。

问题2:CAS操作,先读再比较,然后设置值,步骤这么多,会不会在步骤之间被其它线程干扰导致冲突,这里是不是一个bug?

这个担心纯属多余,因为CAS操作的整个过程是原子操作,它由一条cpu指令完成,并没有把取数据、比较、设置值写在3个cpu指令里。 这条cpu指令叫cmpxchg,它的逻辑如下:

if(accumulator == Destination){
    ZF = 1;
    Destination = Source;
} else {
    ZF = 0;
    accumulator = Destination;
}

看目标值是不是跟计算值相等,如果相等就设置一个跳转标志,并且把原始数据设置到目标里面去,否则跳转标志就不设了。所以,CAS它从指令层面来保证操作的可靠性。

3、调用CAS的Java无锁类AtomicInteger

AtomicInteger类的部分主要内容:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    
    //字段value在当前内存的偏移量,通过位置偏移量,就能得到value在内存中的值。
    private static final long valueOffset;

    //先加载静态常量,然后执行静态代码块(按照静态代码块先后顺序执行),最后调用构造函数。
    //静态代码块:获取字段value在内存的偏移量。
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    /**
     * 这是一个非常重要的字段“value”,用关键词volatile修饰,它是其封装的数据类型,
     * AtomicInteger类所有的操作基本都是对成员变量“value”所做,
     * "value"才是它内部真正的值,AtomicInteger类只是对它的一个包装而已。
     */
    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {无参构造器}.
     */
    public AtomicInteger() {
    }

    /**
     * Gets the current value.
     * 获取当前的value
     * @return the current value
     */
    public final int get() {
        return value;
    }

    /**
     * Sets to the given value.
     * @param newValue the new value
     */
    public final void set(int newValue) {
        value = newValue;
    }

    /**
     * Atomically sets to the given value and returns the old value.
     * 设置一个新值并返回旧值,这是一个满足原子性的操作(Atomically)。
     * @param newValue the new value
     * @return the previous value
     */
    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

    /**
     * Atomically sets the value to the given updated value if the current value {@code ==} the expected value.
     * 参数expect是期望值,参数u是要设置的值,如果期望值等于当前值,即设置成功返回true,否则失败返回false;
     * compareAndSet方法内部用到了一个unsafe对象,从名字可以看到它是一个不安全的操作。
     * 我们知道,java相对于c++或者c更为安全是因为java把有关指针的一些内容给封装起来了或者说屏蔽掉了,而Unsafe类恰恰相反,
     * 它会提供一些相对于java底层类似于指针的一些操作,比如这里的compareAndSwapInt方法,表示针对this对象,
     * 偏移量为valueOffset的数据,看它的期望值是多少,这是一个非常有c感觉的一个操作。
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * <p><a href="package-summary.html#weakCompareAndSet">May fail
     * spuriously and does not provide ordering guarantees</a>, so is
     * only rarely an appropriate alternative to {@code compareAndSet}.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful
     */
    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}
//jdk 1.8.0_51

public final int getAndIncrement() {
	return unsafe.getAndAddInt(this, valueOffset, 1);
}

//getAndAddInt 参数: var1 = this, var2 = valueOffset, var4 = delta
//compareAndSwapInt 参数:var1 = this, var2 = valueOffset, var5 = 旧值, var5 + var4 = 要修改成的值

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;
}

//unsafe.getAndAddInt(this, valueOffset, delta);方法内部使用一个do_while循环,先得到当前的值var5,然后再把当前的值加var4,
//加完之后使用cas原子操作让当前值加var4处理正确。当然cas原子操作不一定是成功的,所以做了一个死循环,当cas操作成功的时候返回数据。
//这里由于使用了cas原子操作,所以不会出现多线程处理错误的问题。比如线程A得到var5为1,线程B也得到var5为1;线程A的var5 + var4值为2,
//进行cas操作并且成功的时候,将var5修改成了2;这个时候线程B得到var5 + var4值仍旧为2,当进行cas操作的时候由于内存中的值已经是2,
//而不是1了;所以cas操作会失败,下一次循环的时候得到的var5就变成了2;也就不会出现多线程处理问题了。

方法getAndIncrement()解释(jdk1.7版本之前):

//取得旧值,在当前值上+1,这是一个线程安全的操作;
public final int getAndIncrement(){
  for(;;){
      int current = get();
      int next = current + 1;
      if(compareAndSet(current,set))
          return current;
  }
}
//可以从其实现中看到,先用get方法取出当前值current,对其加1并赋值给next(这里注意,这个加1一定是对当前值加1,不可能是其它线程改过的数据),
//然后对当前值与+1后的值next进行比较,如果成功,直接返回current。如果在执行完加1之后,有其它线程先一步修改了当前数据,
//会导致实际的current与线程取到的current不相符,所以compareAndSet方法执行失败返回false,程序无法执行到return语句,
//直接回到循环体的开始再重试一遍,直到设置成功为止。从getAndIncrement方法的实现代码中,我们可以看到无锁的算法的基本实现形式,
//即套在一个死循环当中一直作重试,直到自己成功为止。这是一个非常通用的思路,getAndDecrement()、incrementAndGet()等方法也是这种实现思路。

4、实现CAS的Java无锁类Unsafe

Unsafe提供了一些不太安全的操作,它主要是在jdk内部使用,并不对外提供。如果你想要拿到Unsafe的实例,是需要动一些手脚的。它提供了如下操作:

根据偏移量去设置值;
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));

这样就能通过unsafe对象拿到AtomicInteger在成员变量value上的偏移量。

如果说有写过c代码,这里就会比较好理解。

比如说,c里面有一个结构体,定义了两个int类型的成员a与b,如果a的偏移量是0,b的偏移量就是4,只要需要知道结构体所在的基地址(0x7fff0815c0e0),加上偏移量4就能得到b的地址(0x7fff0815c0e4=0x7fff0815c0e0+4),有了b的地址就能对b进行操作。回到java中,也是一样,只不过结构体换成了Class。Class会比c语言中结构体struct复杂一些,它还存有一些对象头部的信息,比如对象年龄等,然后才是字段,比如int value。这个objectFieldOffset方法它拿到的就是value字段在class中的偏移量,所以后面我们就可以根据这个偏移量去做一些设置,比如前方文中中的提到的unsafe.compareAndSwapInt(this,valueOffset,expect,update);就是对当前AtomicInteger对象在valueOffset偏移量的位置上做设置操作:我期望它是expect,更新成update。

5、java无锁类AtomicReference

和封装整数的AtomicInteger相比,AtomicReference封装的是一个对象,它是对封装对象的引用,使用这个对象的引用来对对象进行修改,就能够保证对象的线程安全。

AtomicReference是一个模板,可以用来封装任意类型的数据,它里面的实现与AtomicInteger非常类似,它有成员变量value,也有一个偏移量valueOffset,有get、set方法,也有compareAndSet方法。

如果你有一个对象,希望在修改时保证该对象是线程安全的,就可以使用AtomicReference来包装它。

更详细解释:http://www.hao124.net/article/52

更多原子类:Java并发编程-无锁CAS与Unsafe类及其并发包Atomic

参考:
http://www.hao124.net/article/52

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值