Java 并发中 CAS 的理解

该文章中概念都是从各处的资料总结而来,如果其中有缺少或者不正确的描述,希望各位可以在评论中指出,谢谢!!!


简介

总结:CAS 是一种基于硬件支持的高并发安全操作,它保证了对共享变量的操作是原子性的,从而实现了线程安全,能够避免传统锁机制所带来的性能开销和线程间的竞争,提高了系统的并发处理能力

  • CAS(Compare and Swap)的作用是实现乐观并发控制,它用于解决共享资源的并发访问问题,以保证对共享资源的操作是线程安全的。它避免了使用锁机制(悲观并发控制)带来的开销和竞争,提高了并发性能
  • CAS 是一种基于硬件指令的原子操作,通常由处理器提供支持。在多线程环境下,通过比较当前内存值与期望值是否相等来判断内存中的值是否被修改过。如果值没有被修改,则使用新值更新内存中的值。CAS 操作包括三个参数:内存地址、期望值和新值
  • 在使用 CAS 操作时,如果多个线程同时执行 CAS 操作,只有一个线程会更新内存中的值,其他线程会重试 CAS 操作,直到内存值被更新或者超过了重试次数
  • CAS 操作保证了对共享变量的操作是原子性的。这意味着在任何时刻只有一个线程可以成功地更新共享变量,其他线程无法干扰。它可以避免使用锁所带来的性能开销和线程间的竞争

相比传统锁的优势

总结:CAS 的非阻塞性质允许多个线程同时进行操作,而不需要线程等待和阻塞,避免了线程之间的竞争和串行执行,实现更高的并发性能。而传统的锁机制可能导致线程的串行执行,限制了并发性能的提升

  • 避免了线程的阻塞和唤醒:传统的锁机制在竞争条件下,当一个线程获取到锁后,其他线程需要等待锁释放才能继续执行,这涉及到线程的阻塞和唤醒操作,需要消耗额外的系统资源。而CAS操作是一种乐观的并发控制方式,它不需要线程阻塞和唤醒,而是通过原子性的比较和交换来实现对共享变量的更新,不会造成线程的等待
  • 减少上下文切换:在传统的锁机制中,当一个线程持有锁时,其他线程需要等待,这可能导致线程的频繁上下文切换,而上下文切换是一种开销较大的操作。而CAS操作不需要线程等待,可以避免上下文切换的开销

存在的问题

总结:CAS 虽然有很多优势,但也存在一些问题,如ABA问题、自旋时间过长、只能保证单个变量的原子操作以及弱内存一致性

  • ABA问题:CAS操作只能保证在变量值未被其他线程修改的情况下才能成功修改,但如果变量的值被修改过两次,恢复成原值后,CAS操作将会误认为变量从未被修改过,从而可能产生意想不到的错误。解决ABA问题的一种方式是使用版本号或时间戳来标识变量的修改次数
  • 自旋时间过长:CAS操作如果一直无法成功,会一直重试,这会造成CPU资源的浪费。为了避免这个问题,可以设置一个重试次数的上限或者采用其他锁机制
  • 只能保证一个共享变量的原子操作:如果要对多个变量进行原子操作,就需要对它们分别进行CAS操作。如果对它们进行组合操作,则可能会引入新的并发问题
  • 弱内存一致性:由于CAS操作不会使缓存行失效,因此在多处理器系统中,如果一个处理器修改了共享变量的值,另一个处理器可能不会立即看到变化,需要等待缓存行失效后才能看到。这种现象称为"弱内存一致性",可以通过volatile关键字来解决

乐观并发控制(乐观锁,无锁)

总结:可以将 CAS 算法视为乐观锁的一种实现方式之一, 广泛应用于各种并发算法和数据结构中,如 java.util.concurrent 包中的并发类、无锁算法等

  • 乐观并发控制是一种更广泛的概念,是一种并发控制策略,它强调的是一种乐观的假设和基于这种假设的并发控制策略,即并发访问共享资源的冲突很少发生。在乐观并发控制中,线程在读取共享资源时不加锁,而是在提交修改时发现实际情况与假设不符(即发生冲突),需要进行回滚或重试操作
  • 乐观锁是乐观并发控制的一种具体实现方式,通过记录版本号或时间戳等方式实现的一种具体的并发控制机制。乐观锁在操作共享资源之前获取一个版本号或时间戳等信息,并在提交修改时比较这个版本号是否发生了变化,以判断是否发生了冲突。如果发生了冲突,乐观锁会回滚或重试操作,以保证并发修改的一致性
  • CAS 基于乐观的假设,因此,可以将 CAS 算法视为乐观锁的一种实现方式之一,它通过乐观的方式进行并发控制,避免了悲观锁(如独占锁)带来的阻塞和线程切换开销。CAS 广泛应用于各种并发算法和数据结构中,如非阻塞队列、无锁算法等。然而,CAS也有一些限制和适用场景,例如ABA问题和循环重试等,需要在使用时注意处理

实现层面

Java 层面

在Java 8 中,CAS 由 sun.misc.Unsafe 类提供支持。Unsafe 类是JDK内部提供的一个特殊类,用于执行一些底层的、不安全的操作,包括CAS操作

以下是 Unsafe 类操作 CAS 的方法:

public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);

public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

JVM 层面

HotSpot具体的实现在 源代码的/src/share/vm/prims/unsafe.cpp文件中

以 compareAndSwapInt 方法举例

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt"); // 1行
  oop p = JNIHandles::resolve(obj); // 2行
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); // 3行
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e; // 4行
UNSAFE_END

下面是对代码各行的解释:

  • UnsafeWrapper("Unsafe_CompareAndSwapInt") 这是一个用于执行安全检查的宏,确保在执行此方法之前具有必要的权限
  • oop p = JNIHandles::resolve(obj) 解析Java对象的引用,获取对应的本地对象指针
  • jint* addr = (jint *) index_oop_from_field_offset_long(p, offset) 使用给定的对象指针和偏移量,计算出字段的地址
  • return (jint)(Atomic::cmpxchg(x, addr, e)) == e 使用Atomic::cmpxchg方法进行原子的比较和交换操作

操作系统层面

linux_x86

linux_x86 cmpxchg 方法

inux_x86 平台中 cmpxchg 对应的源代码在:

hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp

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

下面是对代码各行的解释:

  • int mp = os::is_MP() 检查当前系统是否为多处理器环境
  • __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" 使用内联汇编语言编写的指令,执行比较和交换操作。它使用了 LOCK_IF_MP 宏来在多处理器环境下添加 LOCK 前缀,以确保比较和交换的原子性
  • : "=a" (exchange_value) 定义了输出操作数,将比较和交换后的值存储到 exchange_value 变量中
  • : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) 定义了输入操作数,将需要交换的值、比较的值、目标地址和多处理器标志传递给汇编指令
  • : "cc", "memory" 指定了代码中可能影响条件码寄存器(cc)和内存的操作。这样可以确保编译器正确地处理这些操作的顺序和依赖关系
windows_x86

windows_x86cmpxchg 方法

对于 windows_x86 平台,cmpxchg 对应的源代码在

hotspot\src\os_cpu\windows_x86\vm\atomic_windows_x86.inline.hpp

#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:

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

可以看到,windows_x86linux_x86对于cmpxchg方法的实现原理是相同的。只不过于由于不同平台,不同的汇编语法,在代码的写法上存在一些差异而已

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值