java中CAS出现的原因及原理分析

目录

    • 前言
    • 解决方案
    • AtomicInteger源码分析
      • Unsafe
      • valueOffset
      • compareAndSwapInt
      • incrementAndGet
    • 总结

前言

CAS:即compare and swap(比较并替换),CAS需要解决的是多线程并发时线程间切换时间片导致的原子性问题。

我们先看一段代码

public class CasDemo1 {

    static int k = 0;

    public static void main(String[] args) {

        for(int i = 0;i<10;i++) {
            Thread t = new Thread(() -> {
                //每个线程让count自增100次
                try {
                    // 等待所有线程都起来
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int j = 0; j < 100; j++) {
                    k++;
                }
            });
            t.start();
        }


        try {
            // 阻塞2秒,等待线程执行结束
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(k);
    }

}

十个线程,同时让k的值+100,但是结果输出的值是小于等于1000的,这是为什么呢?
原因在于k++这个操作在CPU层面并不保证原子性,会被拆分为

1、查询k的值
2、k=k+1

此时由于线程间会存在时间片的切换,即假如线程A执行到第一步,查询k的值=0,此时CPU进行了切换,线程B成功的执行了1和2,此时对于线程B来说K=1,此时时间片切换到A线程,A线程中的k=0,此时执行2,k+1=1,那么就和我们的预期不同了,并发问题就产生了

解决方案

为了解决上述问题,我们可以使用加锁来避免并发,使用synchronized来加锁,但是synchronized是一种悲观锁的思想,性能会比较差

CAS采用的就是一种乐观锁的思想,即执行时假设没有并发问题,先比较,比较后发现没有被改动则替换,被改动了则自旋重新执行上述1和2步骤直到成功

java已经提供了多种支持并发的cas相关类,主要是在java.util.concurrent.atomic包下,如AtomicInteger、AtomicBoolean、AtomicLong等

AtomicInteger源码分析

我们就看一种AtomicInteger源码的实现

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

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

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

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }
    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final int get() {
        return value;
    }
    
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
 
   ......

    }

Unsafe

Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。如果我们必须要实现自己的基于Unsafe类的功能,则只能通过反射获取Unsafe类,因为Unsafe在创建时加了限制,只有rt.jar包下的类具有权限访问,从Unsafe的getUnsafe可以看出来

Unsafe为我们在Java中提供了直接操作内存的能力。

valueOffset

valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField(“value”)),看到这个代码,可以看出来valueOffset的值是AtomicInteger中的value值在内存中的地址,即偏移量

compareAndSwapInt

unsafe.compareAndSwapInt(this, valueOffset, expect, update)

cas的核心操作,即查询参数1中内存地址为valueOffset的值,当等于参数3时修改为参数4,返回成功,当不相等时返回false
参数1:操作的实例
参数2:操作实例中内存地址
参数3:预期值
参数4:期望修改成的值

incrementAndGet

自增1并且返回

看getAndAddInt方法

 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
        	// 从内存中读取当前对象地址为valueOffset的值
            var5 = this.getIntVolatile(var1, var2);
            // 自旋直到cas操作成功为止,当var5的值仍然是var5时修改为var5+var4
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

即最终会把AtomicInteger中的value+1,并且返回+1后的值

总结

看完AtomicInteger的源码,我们基本上已经懂了cas的实现,即通过Unsafe类来直接比较在内存中的值,如果相同则替换,失败则自旋直到成功,通过直接比较内存中的值来解决上面时间片切换导致的原子性问题

你可能感兴趣的:(java,多线程,cas,原子性)