CAS 原理深入剖析,深入内核源码的那种

系统
CAS 的意思是 compare and swap,比较并交换。有一种情况是,在你更新结果之前,其他有个线程在中途把 A 更新成了 5 ,又更新回了 2。但是在当前线程看起来,没有被改过。这就是 ABA 问题。

 [[385679]]

本文转载自微信公众号「KK架构师」,作者wangkai。转载本文请联系KK架构师公众号。   

一、CAS 简介

CAS 的意思是 compare and swap,比较并交换。

CAS 的示意图如下:

 

比如一个很简单的操作,把变量 A = 2 加 1,结果为 3.

则先读取 A 的当前值 E 为 2,在内存计算结果 V 为 3,比较之前读出来的 A 的当前值 2 和 最新值,如果最新值为 2 ,表示这个值没有被别人改过,则放心的把最终的值更新为 3.

有一种情况是,在你更新结果之前,其他有个线程在中途把 A 更新成了 5 ,又更新回了 2。但是在当前线程看起来,没有被改过。这就是 ABA 问题。

二、CAS 的实现

在 java 中,原子类都是用 cas 来实现的,我们可以看一看源码。

  1. public final int getAndIncrement() { 
  2.     return unsafe.getAndAddInt(this, valueOffset, 1); 

发现是调用了 unsafe 类的 getAndAddInt,继续看这个方法:

  1. public final int getAndAddInt(Object var1, long var2, int var4) { 
  2.     int var5; 
  3.     do { 
  4.         var5 = this.getIntVolatile(var1, var2); 
  5.     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 
  6.  
  7.     return var5; 

这里是一个 while 循环,直到比较成功,来看一下这个 compareAndSwapInt 方法

  1. public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); 

发现这是一个 native 方法,意味着是 c 或者 c++ 写的虚拟机的实现。

在网上找到了 HotSpot 虚拟机的源码,找了一些资料发现了这个 compareAndSwapInt 的 c++ 代码:

在 unsafe.cpp 这个文件的 Unsafe_CompareAndSwapInt 这个方法里

 

发现最终调用的是 Atomic:: cmpxchg 方法,我们再找到 atomic_linux_x86.inline.hpp 这个文件

 

其中有一句 LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"

LOCK_IF_MP 是一个宏,这个宏的定义是:

 

mp 的意思是 multi processor,意思是在多核 cpu 上,要锁一下这个指令。

到这里,结论是,最终调用了一条汇编指令:lock cmpxchg 指令,来实现底层 cas 的。

也就是 cpu 中有一条 cmpxchg 指令。

但是这条指令不是原子的,也就是拿出来和比较是两个操作,中间有可能被别人打断。

所以需要在这个过程加上 lock,意思是,我在对这个内存操作的过程中,不允许被别人打断。

可以简单理解为把内存总线锁住,别人不允许修改这块内存。

 

三、ABA 问题的解决

很简单,可以在数据上加上版本号即可,改了一次就新增一个版本号。

在 Java 中,可以使用 AtomicStampedReference 来解决这个问题

  1. import java.util.concurrent.atomic.AtomicInteger; 
  2. import java.util.concurrent.atomic.AtomicStampedReference; 
  3.  
  4. public class ABASingle { 
  5.  
  6.     public static void main(String[] args) { 
  7.         AtomicInteger atomicInt = new AtomicInteger(100); 
  8.         atomicInt.compareAndSet(100, 101); 
  9.         atomicInt.compareAndSet(101, 100); 
  10.         System.out.println("new value = " + atomicInt.get()); 
  11.         boolean result1 = atomicInt.compareAndSet(100, 101); 
  12.         System.out.println(result1); // result:true 
  13.  
  14.         AtomicInteger v1 = new AtomicInteger(100); 
  15.         AtomicInteger v2 = new AtomicInteger(101); 
  16.         AtomicStampedReference<AtomicInteger> stampedRef = new AtomicStampedReference<AtomicInteger>( 
  17.                 v1, 0); 
  18.  
  19.         int stamp = stampedRef.getStamp(); 
  20.         stampedRef.compareAndSet(v1, v2, stampedRef.getStamp(), 
  21.                 stampedRef.getStamp() + 1); 
  22.         stampedRef.compareAndSet(v2, v1, stampedRef.getStamp(), 
  23.                 stampedRef.getStamp() + 1); 
  24.         System.out.println("new value = " + stampedRef.getReference()); 
  25.         boolean result2 = stampedRef.compareAndSet(v1, v2, stamp, stamp + 1); 
  26.         System.out.println(result2); // result:false 
  27.     } 

 

责任编辑:武晓燕 来源: KK架构师
相关推荐

2020-11-12 18:08:05

JavaLinux多线程

2020-11-20 07:55:55

Linux内核映射

2016-03-14 16:35:40

IT专家网

2009-03-26 10:33:34

Oracle数据块数据库

2009-03-06 16:48:23

数据块原理Oracle

2022-09-05 22:22:00

Stream操作对象

2022-11-14 11:09:36

源码AQS加锁

2010-09-17 15:32:52

JVM工作原理

2010-01-25 17:14:44

Android应用程序

2009-09-27 17:13:36

Hibernate V

2009-07-06 10:44:45

JSP charset

2011-06-03 13:48:18

JavaScript重构

2010-06-03 13:08:51

2010-09-15 15:27:06

CSS hack

2010-05-25 12:59:00

Subversion

2009-09-14 15:12:40

LINQ to XML

2009-12-11 09:42:54

Linux内核源码进程调度

2009-12-11 09:47:23

Linux内核源码进程调度

2018-10-31 15:54:47

Java线程池源码

2009-11-23 15:24:40

点赞
收藏

51CTO技术栈公众号