为什么java有cas_关于java:并发CAS机制你真的理解了嘛深入到操作系统分析

学习Java并发编程,CAS机制都是一个不得不把握的知识点。这篇文章次要是从呈现的起因再到原理进行一个解析。心愿对你有所帮忙。

一、为什么须要CAS机制?

为什么须要CAS机制呢?咱们先从一个谬误景象谈起。咱们常常应用volatile关键字润饰某一个变量,表明这个变量是全局共享的一个变量,同时具备了可见性和有序性。然而却没有原子性。比如说一个常见的操作a++。这个操作其实能够细分成三个步骤:

(1)从内存中读取a

(2)对a进行加1操作

(3)将a的值从新写入内存中

在单线程状态下这个操作没有一点问题,然而在多线程中就会呈现各种各样的问题了。因为可能一个线程对a进行了加1操作,还没来得及写入内存,其余的线程就读取了旧值。造成了线程的不平安景象。如何去解决这个问题呢?最常见的形式就是应用AtomicInteger来润饰a。咱们能够看一下代码:

//应用AtomicInteger定义a

static AtomicInteger a = new AtomicInteger();

public static void main(String[] args) {

Test3 test = new Test3();

Thread[] threads = new Thread[5];

for (int i = 0; i 

threads[i] = new Thread(() -> {

try {

for (int j = 0; j 

//应用getAndIncrement函数进行自增操作

System.out.println(a.incrementAndGet());

Thread.sleep(500);

}

} catch (Exception e) {

e.printStackTrace();

}

});

threads[i].start();

}

}

}

当初咱们应用AtomicInteger类并且调用了incrementAndGet办法来对a进行自增操作。这个incrementAndGet是如何实现的呢?咱们能够看一下AtomicInteger的源码。

* Atomically increments by one the current value.

* @return the updated value

*/

public final int incrementAndGet() {

return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

咱们到这一步能够看到其实就是usafe调用了getAndAddInt的办法实现的,然而当初咱们还看不出什么,咱们再深刻到源码中看看getAndAddInt办法又是如何实现的,

int var5;

do {

var5 = this.getIntVolatile(var1, var2);

} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;

}

到了这一步就略微有点头绪了,原来底层调用的是compareAndSwapInt办法,这个compareAndSwapInt办法其实就是CAS机制。因而如果咱们想搞清楚AtomicInteger的原子操作是如何实现的,咱们就必须要把CAS机制搞清楚,这也是为什么咱们须要把握CAS机制的起因。

二、剖析CAS

1、根本含意

CAS全拼又叫做compareAndSwap,从名字上的意思就晓得是比拟替换的意思。比拟替换什么呢?

过程是这样:它蕴含 3 个参数 CAS(V,E,N),V示意要更新变量的值,E示意预期值,N示意新值。仅当 V值等于E值时,才会将V的值设为N,如果V值和E值不同,则阐明曾经有其余线程做两个更新,则以后线程则什么都不做。最初,CAS 返回以后V的实在值。

咱们举一个我之前举过的例子来阐明这个过程:

比如说给你儿子订婚。你儿子就是内存地位,你本来认为你儿子是和杨贵妃在一起了,后果在订婚的时候发现儿子身边是西施。这时候该怎么办呢?你一气之下不做任何操作。如果儿子身边是你料想的杨贵妃,你一看很开心就给他们订婚了,也叫作执行操作。当初你应该明确了吧。

CAS 操作时抱着乐观的态度进行的,它总是认为本人能够胜利实现操作。所以CAS也叫作乐观锁,那什么是乐观锁呢?乐观锁就是咱们之前赫赫有名的synchronized。乐观锁的思维你能够这样了解,一个线程想要去取得这个锁然而却获取不到,必须要他人开释了才能够。

2、底层原理

想要弄清楚其底层原理,深刻到源码是最好的形式,在下面咱们曾经通过源码看到了其实就是Usafe的办法来实现的,在这个办法中应用了compareAndSwapInt这个CAS机制。因而,当初咱们有必要进一步深刻进去看看:

// compareAndSwapInt 是 native 类型的办法

public final native boolean compareAndSwapInt(

Object o,

long offset,

int expected,

int x

);

//残余还有很多办法

}

咱们能够看到这外面次要有四个参数,第一个参数就是咱们操作的对象a,第二个参数是对象a的地址偏移量,第三个参数示意咱们期待这个a是什么值,第四个参数示意的是a的理论值。

不过这里咱们会发现这个compareAndSwapInt是一个native办法,也就是说再往下走就是C语言代码,如果咱们放弃好奇心,能够持续深刻进去看看。

jobject obj, jlong offset, jint e, jint x))

UnsafeWrapper("Unsafe_CompareAndSwapInt");

oop p = JNIHandles::resolve(obj);

// 依据偏移量valueOffset,计算 value 的地址

jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

// 调用 Atomic 中的函数 cmpxchg来进行比拟替换

return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

UNSAFE_END

下面的代码咱们解读一下:首先应用jint计算了value的地址,而后依据这个地址,应用了Atomic的cmpxchg办法进行比拟替换。当初问题又抛给了这个cmpxchg,实在实现的是这个函数。咱们再进一步深刻看看,假相曾经离咱们不远了。

volatile unsigned int* dest,

unsigned int compare_value) {

assert(sizeof(unsigned int) == sizeof(jint), "more work to do");

/*

* 依据操作系统类型调用不同平台下的重载函数,

这个在预编译期间编译器会决定调用哪个平台下的重载函数

*/

return (unsigned int)Atomic::cmpxchg((jint)exchange_value,

(volatile jint*)dest, (jint)compare_value);

}

皮球又一次被完满的踢走了,当初在不同的操作系统下会调用不同的cmpxchg重载函数,我当初用的是win10零碎,所以咱们看看这个平台下的实现,别着急再往下走走:

jint compare_value) {

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

}

}

这块的代码就有点波及到汇编指令相干的代码了,到这一步就彻底靠近假相了,首先三个move指令示意的是将前面的值挪动到后面的寄存器上。而后调用了LOCK_IF_MP和上面cmpxchg汇编指令进行了比拟替换。当初咱们不晓得这个LOCK_IF_MP和cmpxchg是如何替换的,没关系咱们最初再深刻一下。

假相来了,他来了,他真的来了。

volatile jint* dest, jint compare_value) {

//1、 判断是否是多核 CPU

int mp = os::is_MP();

__asm {

//2、 将参数值放入寄存器中

mov edx, dest

mov ecx, exchange_value

mov eax, compare_value

//3、LOCK_IF_MP指令

cmp mp, 0

//4、 如果 mp = 0,表明线程运行在单核CPU环境下。此时 je 会跳转到 L0 标记处,间接执行 cmpxchg 指令

je L0

_emit 0xF0

//5、这里真正实现了比拟替换

L0:

/*

* 比拟并替换。简略解释一下上面这条指令,相熟汇编的敌人能够略过上面的解释:

*   cmpxchg: 即“比拟并替换”指令

*   dword: 全称是 double word 示意两个字,一共四个字节

*   ptr: 全称是 pointer,与后面的 dword 连起来应用,表明拜访的内存单元是一个双字单元

* 这一条指令的意思就是:

将 eax 寄存器中的值(compare_value)与 [edx] 双字内存单元中的值进行比照,

如果雷同,则将 ecx 寄存器中的值(exchange_value)存入 [edx] 内存单元中。

*/

cmpxchg dword ptr [edx], ecx

}

}

到这一步了,置信你应该了解了这个CAS真正实现的机制了吧,最终是由操作系统的汇编指令实现的。

3、CAS机制的优缺点

(1)长处

一开始在文中咱们已经提到过,cas是一种乐观锁,而且是一种非阻塞的轻量级的乐观锁,什么是非阻塞式的呢?其实就是一个线程想要取得锁,对方会给一个回应示意这个锁能不能取得。在资源竞争不强烈的状况下性能高,相比synchronized分量锁,synchronized会进行比较复杂的加锁,解锁和唤醒操作。

(2)毛病

毛病也是一个十分重要的知识点,因为波及到了一个十分驰名的问题,叫做ABA问题。假如一个变量 A ,批改为 B之后又批改为 A,CAS 的机制是无奈觉察的,但实际上曾经被批改过了。这就是ABA问题,

ABA问题会带来大量的问题,比如说数据不统一的问题等等。咱们能够举一个例子来解释阐明。

你有一瓶水放在桌子上,他人把这瓶水喝完了,而后从新倒上去。你再去喝的时候发现水还是跟之前一样,就误以为是刚刚那杯水。如果你晓得了假相,那是他人用过了你还会再用嘛?举一个比拟黄一点的例子,女朋友被他人睡过之后又回来,还是之前的那个女朋友嘛?

ABA能够有很多种形式来解决,在其余的文章曾经给出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值