java cas底层源码学习

问题:cas怎么保证共享变量是原子性的?

1、demo演示

cas demo代码:

public class CasCounter {
    private volatile int count = 0;

    private static long offset;
    private static Unsafe unsafe;
    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            unsafe = (Unsafe) f.get(null);
            offset = unsafe.objectFieldOffset(CasCounter.class.getDeclaredField("count"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public void increment() {
        int before = count;
        // 失败了就重试直到成功为止
        while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) {
            before = count;
        }
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        CasCounter casCounter = new CasCounter();
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            threadPool.submit(() -> {
                for (int j =0; j < 10000; j++) {
                    casCounter.increment();
                }
            });
        }
        threadPool.shutdown();
        Thread.sleep(2000);
        // 打印1000000
        System.out.println(casCounter.getCount());
    }
}

其中compareAndSwapInt是native方法,此处可以看出,原子性是在native方法的具体实现中保证的,那该native方法是如何保证共享变量的原子性的?接下来进行compareAndSwapInt源码的阅读。

 

2、compareAndSwapInt底层源码阅读

2.1、源码获取

可在openjdk中找到相关native源码:

http://hg.openjdk.java.net/

https://github.com/AdoptOpenJDK/openjdk-jdk8u

这里以jdk8u为例,具体位置如下:

链接:http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/file/37240c1019fd/src/share/vm/prims/unsafe.cpp

2.2、compareAndSwapInt源码

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  // 根据偏移量获取内存地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  // 执行内联汇编程序
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

其中windows对应的cmpxchg()方法如下:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

linux对应的cmpxchg()方法如下:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

可以看到cmpxchg是使用了汇编语言,最核心的比较并交换功能是通过指令cmpxchg实现的,指令具体含义可以参考如下链接:

https://www.felixcloutier.com/x86/cmpxchg

这里windows中只对cmpxchg指令加了LOCK前缀,如果eax、ecx、edx寄存器内的数据仍然是共享的,还是存在数据修改的可能,我的理解是这样的:cpu上下文切换时,会保存cpu上下文数据(包括寄存器和程序计数器),然后加载下一个线程,直到再次调度上一个线程时,把之前保存的上下文数据再次加载进来,保证上一个线程不受影响。也就是说,__asm代码块前和后有保存和恢复机制。

参考来源:http://www.linfo.org/context_switch.html

Context switching can be described in slightly more detail as the kernel (i.e., the core of the operating system) performing the following activities with regard to processes (including threads) on the CPU: (1) suspending the progression of one process and storing the CPU's state (i.e., the context) for that process somewhere in memory, (2) retrieving the context of the next process from memory and restoring it in the CPU's registers and (3) returning to the location indicated by the program counter (i.e., returning to the line of code at which the process was interrupted) in order to resume the process.

以及:https://docs.microsoft.com/en-us/cpp/assembler/inline/using-and-preserving-registers-in-inline-assembly?view=vs-2019

When using __asm to write assembly language in C/C++ functions, you don't need to preserve the EAX, EBX, ECX, EDX, ESI, or EDI registers. For example, in the POWER2.C example in Writing Functions with Inline Assembly, the power2 function doesn't preserve the value in the EAX register. However, using these registers will affect code quality because the register allocator cannot use them to store values across __asm blocks. In addition, by using EBX, ESI or EDI in inline assembly code, you force the compiler to save and restore those registers in the function prologue and epilogue.

参考:

https://www.jb51.net/article/161858.htm

https://www.cnblogs.com/snowater/p/8303698.html

https://www.zhihu.com/question/266359785/answer/922874065

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值