CAS是Compare And Swap的简写,意思是比较并且交换。基于CAS机制,JDK实现了很多的原子变量类。例如AtomicInteger,AtomicBoolean,AtomicReference等等。所谓原子就是指不可分割的意思,换句话说就是我的一个操作,比如制作蛋糕。但是制作蛋糕分为很多步骤,例如第一步要先和面,然后烘焙,最后喷上奶油。那么原子操作的意思就是要么我按照这三个步骤把蛋糕完成,即使现在来了客人要买蛋糕,也不会停下来做蛋糕的过程去招呼客人。要么这个蛋糕就不做,连和面都不执行。
在Java多线程的情况下我们经常会使用synchronized关键字来保证线程安全。其实synchronized关键字也可以实现原子操作。只不过这个原子操作是用锁来实现的,是一个比较重的原子操作实现。假如说一个方法的内部只是进行了一个 i++ 的操作,那么使用synchronized就会非常影响性能。而CAS机制的出现则是为了解决这种问题。CAS机制的实现是利用了现代CPU支持的CAS指令,也就是说原子操作是由CPU保证的。
如下图所示,假设现在有三个线程要对count = 0这样的一个数值进行count++操作:
![](https://img.haomeiwen.com/i7789342/1c20a2f67eafbce0.png)
那么首先三个线程会从内存中读取一份count = 0 保存在线程自己内部,然后去进行count++操作。当执行完成之后,要把执行完之后的结果刷新到内存中。于是根据现代CPU支持的CAS指令(一条指令同时只能由一个线程执行),于是此时其中一个线程则会首先去比较内存中count的值是否是0。如果是0的话,那么把计算的结果与之前的旧值进行交换。
![](https://img.haomeiwen.com/i7789342/0c3200a08822c33d.png)
反正如果在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机制则有一下三个弊端:
-
ABA问题
所谓ABA就是假设现在有两个线程,线程1的目的是想把A变成B,那么它就要在把A变成B的时候就要检查A是否和读取的时候一样。但是线程2在线程1进行检查之前先把A变成了C之后又迅速的变回了A,那么当线程1去进行比较的时候发现和之前的旧A一样,于是愉快的把A改成了B。但A确实被修改过了。如果线程1不在乎这种情况的话,ABA就不是问题了。那如何解决呢?其实很简单,在A上增加一个计数器,要求每一个线程在修改A的时候要加上一个版本戳。JDK中也有对应的实现,分别是AtomicMarkableReference和AtomicStampedReference。他们之间的区别是AtomicMarkableReference关心的变量有没有被修改过,而AtomicStampedReference不但关心有没有被修改过,还关心被修改的次数。 -
开销问题
由于CAS机制采用的是类似于自旋的方式来实现的原子性,那么在激烈竞争的情况下就会出现循环很多次的情况,也就是CPU会一直处忙碌状态,就会发现CPU的使用率一直居高不下 -
只能保证一个共享变量的原子操作
CAS机制是需要比较内存中某个变量的值,换句话说针对的是内存中的某个地址进行比较。一个地址只能对应一份数据,因此CAS操作就无法保证了。例如在某一个代码块内要同时对变量A,变量B和变量C进行修改。那对于如何解决这个问题JDK也提供了对应的类来实现:AtomicReference。具体方式就是把所要修改的变量变成一个对象的成员属性,那么到时候只需要对这个对象进行操作就可以了
悲观锁和乐观锁概念
悲观锁指在任何时候都觉得我要操作的值是不安全的,那么我就首先拿到锁,其他任何线程都不可以进来,直到完成任务。例如synchronized
乐观锁指在我进行执行任务的时候不会有其他线程来进行干扰,先把工作做了。等做完了之后再通过某一种机制来检查是否在执行任务的期间有其他线程执行过这个任务。如果没有则可以放心的结束,否则再重新执行一遍。例如CAS机制
网友评论