热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JAVACAS问题原理

JAVA多线程中,线程安全问题是随处可见的问题,JAVA早期版本使用synchronized来保证线程同步。JDK1.5之后引入CAS。文章目录一、CA

JAVA多线程中,线程安全问题是随处可见的问题,JAVA早期版本使用synchronized来保证线程同步。JDK1.5之后引入CAS。




文章目录

  • 一、CAS的概念
  • 二、以AtomicInteger.java为例
    • 1、getAndIncrement()方法
    • 2、getAndAddInt()方法
    • 3、weakCompareAndSetInt()方法
    • 4、compareAndSetInt()方法
    • 5、atomic_cmpxchg()方法
  • 总结




一、CAS的概念

  CAS(全称:compare and swap,比较和及交换),CAS至少需要三个操作数,当前内存位置(VALUE)、预期的值(expectedValue)、新值(newValue),如图。当多个线程对同一个变量修改的时候,CAS的做法是:找到当前内存的存储的值,然后和预期的值进行比较,如果当前内存位置的值和expectedValue相等,则自动将该值设置为newValue,如果不可相等,将再次获取当前位置的值比较,直到当前内存的值和expectedValue相等,然后才将内存的值设置为newValue。简单的说,就是不断的进行比较,直到成功后设置新值,保证设置新值的时候不会去覆盖别的线程的设置的值。
在这里插入图片描述


二、以AtomicInteger.java为例

  JAVA中对AtomicInteger.java的注释如下:可以原子方式更新int的值,也就是说,AtomicInteger.java这个类中,涉及到的相关方法在操作行基本上都是原子性的,基本都能保证线程安全问题。


1、getAndIncrement()方法

  JAVA中对getAndIncrement()的注释如下:使用VarHandle.getAndAdd指定的内存效果,以原子方式递增当前值,相当于getAndAdd(1)。也就是说,当我们调用getAndIncrement()的时候,相当于系统帮我们以原子操作进行了i++操作。


2、getAndAddInt()方法

  当我们调用getAndIncrement()时,方法会调用Unsafe.java类中的getAndAddInt()方法,源码如下:

public final int getAndIncrement() {return U.getAndAddInt(this, VALUE, 1);
}public final int getAndAddInt(Object o, long offset, int delta) {int v;do {v = getIntVolatile(o, offset);} while (!weakCompareAndSetInt(o, offset, v, v + delta));return v;}

  v相当于是i++中的i,o是当前对象,也就是AtomicInteger.java对象,因为这个o是从上层传递下来的,offset为偏移量,在UnSafe.java中,通过获取o+offset的位置的值,然后调用weakCompareAndSetInt()方法来进行原子操作。


3、weakCompareAndSetInt()方法

  到这里我们可以发现,其实,v就是我们期望的值,x就是我们想要改变的值。

public final boolean weakCompareAndSetInt(Object o, long offset,int expected,int x) {return compareAndSetInt(o, offset, expected, x);}

4、compareAndSetInt()方法

  compareAndSetInt()方法是一个native方法,也就是用c++来实现的,我们需要在JDK官网上下载JDK源码,找到/openjdk/src/hotspot/share/prims/unsafe.cpp文件中的Unsafe_CompareAndSetInt()方法,实际上它调用了atomic_cmpxchg()方法。compareAndSetInt()返回一个boolean类型的值,这个boolean类型的值是atomic_cmpxchg()返回的寄存器中的值和新值比较的结果。如果相等,就说明CAS成功,新值设置成功,返回true,否则就说明CAS失败,新值设置失败,返回false。源码如下:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) {oop p &#61; JNIHandles::resolve(obj);if (p &#61;&#61; NULL) {volatile jint* addr &#61; (volatile jint*)index_oop_from_field_offset_long(p, offset);return RawAccess<>::atomic_cmpxchg(x, addr, e) &#61;&#61; e;} else {assert_field_offset_sane(p, offset);return HeapAccess<>::atomic_cmpxchg_at(x, p, (ptrdiff_t)offset, e) &#61;&#61; e;}
} UNSAFE_END

5、atomic_cmpxchg()方法

  转到/openjdk/src/hotspot/os_cpu/linux_x86/atomic_linux_x86.hpp文件中&#xff0c;查看atomic_cmpxchg具体的系统平台的实现&#xff0c;源码如下&#xff1a;

template<>
template<typename T>
inline T Atomic::PlatformCmpxchg<8>::operator()(T exchange_value,T volatile* dest,T compare_value,atomic_memory_order /* order */) const {STATIC_ASSERT(8 &#61;&#61; sizeof(T));__asm__ __volatile__ ("lock cmpxchgq %1,(%3)": "&#61;a" (exchange_value): "r" (exchange_value), "a" (compare_value), "r" (dest): "cc", "memory");return exchange_value;
}

  到这里已经是汇编指令的级别代码了。
    “r” (exchange_value), “a” (compare_value), “r” (dest)&#xff1a;表示将compare_value的值存入eax寄存器&#xff0c;exchange_value、dest存入任意的通用寄存器。
    “&#61;a” (exchange_value)&#xff1a;表示将rax寄存器的值赋值给exchange_value变量。
    lock&#xff1a;lock指令就是锁总线&#xff0c;来保证这块内存的互斥性。
    cmpxchgq %1,(%3)&#xff1a;实际上是cmpxchgq exchange_value, (dest)。首先把比较eax寄存器的值和dest的值是否相等&#xff0c;如果相等&#xff0c;则把exchange_value的值写入到dest指向的地址中&#xff0c;如果不相等&#xff0c;则把dest中的值写入到eax寄存器中。最中返回exchange_value&#xff0c;而exchange_value存储的是eax寄存器中的值&#xff0c;


总结

  本文主要是对JAVA多线程中CAS问题进行了一系列的探讨。内容设计面较广&#xff0c;深入JAVA源码以及C&#43;&#43;源码&#xff0c;深入了解了CAS工作的原理。


推荐阅读
author-avatar
淇蒴_192
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有