CAS
什么是CAS?
CAS全称Compare and swap(比较与替换)
, 该技术用于并发更新一个值数据时,使用不加锁的方式去安全的更新,
因为加锁是一种比较昂贵的操作,不加锁又无法保证多线程下的安全性,CAS
在赋值时,有三个关键值,
- 预期值
- 新值
- 旧值
该操作的原子性由操作系统保证,在赋值时,使用预期值
和旧值
对比,当这两个值相等时,才将内存中的值替换为新值
,否则就失败(和乐观锁同理, 也就可以理解CAS就是一种乐观锁),
失败后,通过一个循环的方式(称之为自旋
),重新去尝试赋值,直到成功为止,这就是CAS的核心概念了,Java中的Atomic
开头相关的类,都是用CAS去做的, 可以在并发情况下提高很大的性能,
下图是Unsafe
类的getAndAddInt
方法,var1
是一个java对象, var2
是java对象中某field的偏移量
,var4
是想要增加的数
, var5
是内存中的值,
使用var1 + var2
获得到内存中的值,也就是var5(预期值)
, 随后用 var5 + var4
累加出来新值
,
最终使用本地原子性方法compareAndSwapInt
, 使用var5(预期值)
与 var1 + var2(内存值)
对比,如果成功, 就将var5 + var4(新值)
放入到内存中替换,
否则不停的循环尝试以上步骤:
因为加锁是一种比较昂贵的操作(类似悲观锁),所以在多线程环境下的共享变量建议都使用对应的Atomic
类去做处理(类似乐观锁),
例如 Integer
使用AtomicInteger
, Boolean
使用AtomicBoolean
,Long
使用AtomicLong
。
Atomic另一种用途
Atomic
除了可以在并发环境下提高性能外,还有另外一种用途, 来看一下下面的Demo,
可以看到在下图中,我们在new Thread
外面创建了一个变量counter
, 在线程内部对该变量进行累加重新赋值的操作,
但是编译器告诉我们无法操作外部的counter
, 原因是因为,我们虽然看起来在新的线程中引用了外面的counter
变量,但其实运行时,JVM将外部的counter
变量拷贝了一份到新线程的方法栈中,这时候两个counter
已经没有什么关系了,
JVM
为了防止开发者误以为在线程中可以直接改变外部变量的值,所以限制了这种操作。
但是再思考一下,仅仅是因为防止开发者误以为可以直接改变外部变量的值,才做的相应的处理的吗?
我们知道线程栈桢中的变量是私有的,只有在当前方法栈中才可以使用,当前方法栈一旦销毁,内部的变量也会被销毁,
如果允许其他的线程修改当前方法栈中的变量,那就颠覆了java对方法栈中局部变量的规定,颠覆了线程模型,所以这个其实还是因为java的线程模型限制的,
推荐文章: https://zhuanlan.zhihu.com/p/82921974 。
java不允许我们在另一个线程中修改一个方法栈中的局部变量,但是如果我们恰巧就是需要修改,这时候就可以采用Atomic
类了,看下方代码:
使用AtomicInteger
对象,因为我们改变的是对象中的属性value值,而不是直接修改了局部变量counter
的值,
这是被允许的,这就是Atomic
除了提升并发性能外的另一种用途。