关于CAS思想的一些思考

前言

最近在复习java的一些基础知识,重新对于一些比较模糊的概念进行了一番梳理。用一些实际的案例来加深自己的理解证实是一个行之有效的好办法。
关于CAS其实早有耳闻,在刚刚开始接触java的时候遍已经听闻过它的大名,然而所学尚欠火候,始终无法得其真谛,现今虽不能自信地说完全懂得,但好歹有一些自己总结的东西可以聊聊。

乐观锁

首先在谈及CAS之前,还是得先聊聊乐观锁这个概念。顾名思义,乐观乐观即总是倾向于把事情往好的方面去想。 锁呢,大家应该都不陌生了。
在并发环境中,为了保证数据的安全性而设立的一种保障机制。字大家都认识,但有时候组合起来吧就懵圈了。咳咳,回到正题上来。
乐观锁呢,实际属于一种概念,我们可以理解为接口定义,定义它是干什么用的,而CAS呢其实是它的一种实现。可以试着这么去理解,
即在对目标数据进行修改前,我总是倾向于相信这个数据当前没有人修改,只有我自己。那我就直接读取数据,然后修改成我想要的值,但是为了确保万无一失(想着没人改不代表就没人改)
在准备提交的时候呢再校验一下是不是有人修改了。因为我只是倾向于相信没有人修改,但是实际有没有呢这个是只有验证才知道的。
(我们暂时先不去管怎么个验证法)因为它还不是具体实现机制。如果没人修改就直接提交,
如果有人修改就说明自己的倾向判断错了,需要重新读取最新数据,回到这个验证的过程,一直重复这个过程直到确定没人修改再提交。

CAS

CAS(Compare And Set)是乐观锁的一种具体实现机制,从名字上就能看出来是基于比较的一种机制。上文的介绍中我没有详细进行说明的验证方法,在CAS中其实是这么执行的。读取目标数据后,缓存起来。
然后带着想要设置的值进入准备提交的阶段,这时再读取一次目标数据,比较缓存值和当前目标值是否一致(目标值必须是内存可见的,在java中使用volatile修饰)。如果数据一致则代表当前没有其他人(线程)操作目标。
这时就可以放心提交啦,但是如果不相等就需要再次回到读取验证和预提交的环节。这个环节可以称之为“自旋”。(因为如果线程竞争比较激烈的时候,它就一直在循环读取比较啦,宏观表象就是转圈)。

ABA问题

关于上述基本的CAS原理,大家应该都看明白了,因为还是比较简单的。但是这个机制真的没问题吗,其实暴露了一个称之为“ABA”的问题。细心的小伙伴想必应该已经发现了,
就是我们验证缓存值和当前目标值相不相等,以此来作为是否有线程修改的依据其实是有问题的。如果我们缓存值为0,我们的线程想把它更改为1,这个时候我们还没有读取值,但是有另一个线程把它改为了1随后马上又改成了0,
这时我们的线程读取目标值也为0。虽然结果一样,但是中间过程却隐藏了,程序无法感知,这就是“ABA”问题。

解决ABA

那如何解决ABA问题呢,我们引入一个版本号的概念,目标值初始化时给定一个版本,例如1.0,在修改目标值的时候我们就加一个版本。以后每次判断目标值与缓存值相不相等的时候必须校验值和版本是否一致,
其中有一个不一致的时候必须重新从内存读写最新的值。这样就不存在“ABA”问题了。

悲观锁

以上关于乐观锁和CAS的介绍就差不多了,相对乐观锁其实就有悲观锁的说法。概念上也是很好理解的,在操作目标数据时,总是倾向于认为有人也在修改数据,为了保证数据安全,我必须先锁住当前目标才敢操作(排他性)。
在java当中synchronized就是悲观锁的一种实现,是不是很好理解呢,被它修饰的方法都是同步执行的。

效率比较

从实现上来讲,其实很直观的能看出乐观锁比悲观锁的效率要更高。因为悲观锁是宁可错杀一千,不可放过一个,一律同步执行。就synchronized这种JVM层级的锁来说早期实现是比较重的,执行效率很慢,
但是经过这么多年的版本迭代更新,它的效率已经不可同日而语了。但是具体的效率还是得看线程并发环境,如果竞争非常激烈,乐观锁因为长时间的自旋其实会拖慢效率甚至比使用悲观锁还要慢。

应用

  • jdk1.8当中ConcurrentHashMap是直接使用的 CAS + Synchronized来保证线程安全的。(1.7当中使用的是分段可重入锁ReentrantLock)相反的HashTable和Collections.synchronizedMap()则直接使用的是synchronized这种悲观锁来保证线程安全,
    在一般情况下前者的效率还是大大比后者好的。
  • CAS在自旋锁当中也有运用,只是把资源换成了锁,有兴趣的小伙伴可以自行查阅,是很好理解的
  • java轻量级锁属于乐观锁,重量级锁的实现属于悲观锁
  • 基于自旋锁可以动态根据竞争压力调整锁的策略,从偏向锁转化为轻量级或者重量级锁(锁状态:无锁状态、偏向锁、轻量级锁和重量级锁,单向升级从低到高)JDK 1.6中默认是开启偏向锁和轻量级锁的。

后记

掌握CAS的思想,对于创建复杂多变的多线程应用是很有帮助的,借此可以实现轻量级的线程安全机制,对于理解很多复杂的程序设计思想(流行的redis事务等等)也有很好的果效。这期的分享完啦,前路慢慢,以此为记。