大话CAS,那些事--无锁才是真的快。。。。。

本文详细探讨了CAS(Compare And Swap)无锁算法在Java中的应用,通过AtomicInteger类举例说明其工作原理。CAS利用unsafe类的native方法实现原子操作,保证了并发环境下的安全性。然而,CAS存在ABA问题,即在高并发场景下可能无法察觉到值在A到B再到A的过程中的变化。为了解决这个问题,文章提到了AtomicStampedReference作为解决方案,通过版本号实现乐观锁。

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

话说这个synchronized关键字保证了,多线程环境下加锁 强行-单线程执行。可惜呀,这个性能太差劲了,因此就有了 CAS一种无锁算法的出现。

我们今天从 java.util.concurrent.atomic.AtomicInteger 并发包下的这个工具类慢慢道来

AtomicInteger atomicInteger=new AtomicInteger(5);
	atomicInteger.compareAndSet(5,2);
	public final boolean compareAndSet(int expect, int update);
	预期值,更新值

CAS— compareAndSet,比较并交换。这个又牵扯到了前面的JMM。上图 加深理解
在这里插入图片描述
从源码接口认识几个概念,预期值,更新值
结合图片,啥是预期值?线程工作内存的值—如果这个值和主内存的值相同,就修改为更新值、compareAndSet 返回true。

那么问题来了,线程t1执行compareAndSet 操作的时候不怕其他线程打扰吗?接着往下看…

	public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        //this 当前对象,valueOffset 内存偏移地址--直接定位内存。
        
    }
	public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

	private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
            // valueOffset 内存偏移量
        } catch (Exception ex) { throw new Error(ex); }
    }
    private volatile int value; //线程可见
    

通过追踪代码,发现直接调到了 unsafe类的compareAndSwapInt 方法上,这是一个native方法。
1、被native关键字修饰的方法叫做本地方法
2、一个Native Method就是一个java调用非java代码的接口
3、该方法的实现由非java语言实现,比如C,汇编 基于硬件操作。
总的来说,unsafe 是入口,他的 native 修饰的方法是原子操作的,直接操作底层系统-内存、不会被其他线程打断。
在这里插入图片描述

这一切都得益于 unsafe 这个类,我们认识一下,位于jdk— rt.jar、package sun.misc.Unsafe;

public final class Unsafe {
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;
    public static final int ARRAY_BOOLEAN_BASE_OFFSET;
    public static final int ARRAY_BYTE_BASE_OFFSET;
    public static final int ARRAY_SHORT_BASE_OFFSET;
    public static final int ARRAY_CHAR_BASE_OFFSET;
    public static final int ARRAY_INT_BASE_OFFSET;
    public static final int ARRAY_LONG_BASE_OFFSET;
    public static final int ARRAY_FLOAT_BASE_OFFSET;
    public static final int ARRAY_DOUBLE_BASE_OFFSET;
    public static final int ARRAY_OBJECT_BASE_OFFSET;
    public static final int ARRAY_BOOLEAN_INDEX_SCALE;
    public static final int ARRAY_BYTE_INDEX_SCALE;
    public static final int ARRAY_SHORT_INDEX_SCALE;
    public static final int ARRAY_CHAR_INDEX_SCALE;
    public static final int ARRAY_INT_INDEX_SCALE;
    public static final int ARRAY_LONG_INDEX_SCALE;
    public static final int ARRAY_FLOAT_INDEX_SCALE;
    public static final int ARRAY_DOUBLE_INDEX_SCALE;
    public static final int ARRAY_OBJECT_INDEX_SCALE;
    public static final int ADDRESS_SIZE;
    private static native void registerNatives();
    private Unsafe() {
    }
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
	public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
    public native Object getObjectVolatile(Object var1, long var2);
    public native void putObjectVolatile(Object var1, long var2, Object var4);

在这里插入图片描述
总算摸清楚了,关键之处。。。。累吐血,接着我们探索一下另一个问题。针对i++ 我们经常用 atomicInteger.incrementAndGet(); 代替,说什么自旋–无并发,今天借着这个机会看一下。

public final int incrementAndGet() {
       return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
 }
 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));
		//oh,my god 原来此处,每次都会从主内存取最新值,和线程自己工作内存中对比--简称自旋、否则不会跳出循环。。。。。
        return var5;
  }

在这里插入图片描述
jj,一个复杂的问题 终于被阐述清楚了…

任何事物都有两面性,CAS也不例外…
在这里插入图片描述
ABA 后续补充。。。。
上面引出了ABA这个问题,今天接着细细研究一下。有这样一种场景…
t1线程执行慢,t2线程执行快–t2 执行期间主物理内存值从A->B->A, t1进行CAS时拿到的还是A,期间的变化未知
因为 CAS是取出内存某个时刻的数据比较替换,无法感知时间差导致的数据变化、可能会导致一些问题。上图解释
在这里插入图片描述
t1执行2秒,t2执行10秒,t1 把主内存的值由 A->B->A,t2 进行CAS时拿到的还是A,期间的变化不能感知。
针对这个问题如何解决?版本号–乐观锁,
java.util.concurrent.atomic.AtomicStampedReference 使用这个工具类即可,

 自定义原子对象,
   AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference(100,1);
	new Thread(()->{
            int stamp=stampedReference.getStamp();
            System.out.println("t3:"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println("t3 更新后版本号:"+stampedReference.getStamp());
        },"t3").start();

        new Thread(()->{
            int stamp=stampedReference.getStamp();
            System.out.println("t4:"+stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t4 工作线程版本号:"+stamp+"——>主内存版本号:"+stampedReference.getStamp());
            System.out.println("t4:"+stampedReference.compareAndSet(100,120,stamp,stamp+1)+"当前值:"+atomicInteger.get());
        },"t4").start();
  //执行结果:
  	t4:1
	t3:1
	t3 更新后版本号:3
	t4 工作线程版本号:1——>主内存版本号:3
	t4:false当前值:100

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值