Java并发编程四之CAS的基本原理

2020-05-25  本文已影响0人  echoSuny

CAS是Compare And Swap的简写,意思是比较并且交换。基于CAS机制,JDK实现了很多的原子变量类。例如AtomicInteger,AtomicBoolean,AtomicReference等等。所谓原子就是指不可分割的意思,换句话说就是我的一个操作,比如制作蛋糕。但是制作蛋糕分为很多步骤,例如第一步要先和面,然后烘焙,最后喷上奶油。那么原子操作的意思就是要么我按照这三个步骤把蛋糕完成,即使现在来了客人要买蛋糕,也不会停下来做蛋糕的过程去招呼客人。要么这个蛋糕就不做,连和面都不执行。
在Java多线程的情况下我们经常会使用synchronized关键字来保证线程安全。其实synchronized关键字也可以实现原子操作。只不过这个原子操作是用锁来实现的,是一个比较重的原子操作实现。假如说一个方法的内部只是进行了一个 i++ 的操作,那么使用synchronized就会非常影响性能。而CAS机制的出现则是为了解决这种问题。CAS机制的实现是利用了现代CPU支持的CAS指令,也就是说原子操作是由CPU保证的。
如下图所示,假设现在有三个线程要对count = 0这样的一个数值进行count++操作:



那么首先三个线程会从内存中读取一份count = 0 保存在线程自己内部,然后去进行count++操作。当执行完成之后,要把执行完之后的结果刷新到内存中。于是根据现代CPU支持的CAS指令(一条指令同时只能由一个线程执行),于是此时其中一个线程则会首先去比较内存中count的值是否是0。如果是0的话,那么把计算的结果与之前的旧值进行交换。



反正如果在compare的时候,发现count的值与之前读进来的值不相同,例如count等于1了,肯定是其他的线程对count进行修改了,那么当前线程则会去重新从内存中再读一次count的值(也就是把count = 1读进来)并且进行++操作,然后则会重新去进行compare,看现在count的值是不是1。是就进行交换,不是则重复上述的步骤,直到比较的时候count的值和进行计算之前读取的count相等则进行交换并写入内存。
为什么CAS操作性能要优于synchronized

我们知道现代CPU执行一条指令的时间周期大概是0.6纳秒。而使用synchronized关键字则会发生线程阻塞。线程阻塞必定要发生上下文切换。而一次上下文切换的时间大概是500020000个时间周期,算下来大概是35毫秒。而一次线程的阻塞到被唤醒则是要进行两次上下文切换,时间要乘以2的。这样一对比就很明显了,机制CAS指令循环执行了1000次也不过就是600纳秒。

CAS机制能否取代锁机制呢?

答案肯定是否定的。任何事物都有其两面性,有利必有弊。那么CAS机制则有一下三个弊端:

悲观锁和乐观锁概念

悲观锁指在任何时候都觉得我要操作的值是不安全的,那么我就首先拿到锁,其他任何线程都不可以进来,直到完成任务。例如synchronized
乐观锁指在我进行执行任务的时候不会有其他线程来进行干扰,先把工作做了。等做完了之后再通过某一种机制来检查是否在执行任务的期间有其他线程执行过这个任务。如果没有则可以放心的结束,否则再重新执行一遍。例如CAS机制

上一篇 下一篇

猜你喜欢

热点阅读