Java并发编程之CAS原理分析
在并发编程中,我们经常需要处理多线程对共享资源的访问和修改。那么如何解决并发安全呢?
一. 解决并发安全问题的方案
最粗暴的方式就是使用 synchronized 关键字了,但它是一种独占形式的锁,属于悲观锁机制,性能会大打折扣。olatile 貌似也是一个不错的选择,但 volatile 只能保持变量的可见性,并不保证变量的原子性操作。
相比之下,CAS(Compare And Swap)是一种乐观锁机制。它不需要加锁,是一种无锁的原子操作。
二. 悲观锁和乐观锁
1. 悲观锁
对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。
2. 乐观锁
对于乐观锁来说,它总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。一旦多个线程发生冲突,乐观锁通常使用一种称为 CAS 的技术来保证线程执行的安全性。
由于乐观锁假想操作中没有锁的存在,因此不太可能出现死锁的情况,换句话说,乐观锁天生免疫死锁。
3. 使用场景
乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;
悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
三、什么是CAS
CAS 是什么,它的英文全称是 Compare-And-Swap,中文叫做“比较并交换”,它是一种思想、一种算法。
CAS算法有3个基本操作数:
内存地址V
旧的预期值A
要修改的新值B
在并发场景下,各个代码的执行顺序不能确定,为了保证并发安全,我们可以使用普通的互斥锁,比如Java的 synchronized, ReentrantLock等。而CAS的特点是避免使用互斥锁,当多个线程并发使用CAS更新同一个变量时,只有一个可以操作成功,其他都会失败。而且用CAS更新失败的线程并不会阻塞,会快速失败并返回一个失败的状态,允许你再次尝试。
四、CAS的原理
CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。
CAS操作是原子的,即在同一时刻只有一个线程能够成功执行CAS操作。
CAS的基本步骤如下:
读取共享变量的当前值。
比较当前值与期望值。
如果相等,则使用新的值替换当前值。
如果比较失败,则说明其他线程已经修改了共享变量,需要重新读取当前值并重试操作。
CAS操作是一种无锁的操作,避免了传统锁机制中的竞争和阻塞,从而提高了并发性能。
java.util.concurrent 包很多功能都是建立在 CAS 之上,如 ReenterLock(可重入锁) 内部的 AQS,各种原子类,其底层都用 CAS来实现原子操作。
五、CAS的问题
CAS和锁都解决了原子性问题,和锁相比没有阻塞、线程上下文你切换、死锁,所以CAS要比锁拥有更优越的性能,但是CAS同样存在缺点。
1. 只能保证一个共享变量原子操作
CAS只能针对一个共享变量使用,如果多个共享变量就只能使用锁了,当然如果有办法把多个变量整成一个变量,利用CAS也不错,例如读写锁中state的高低位。
2. 自旋时间太长
当一个线程获取锁时失败,不进行阻塞挂起,而是间隔一段时间再次尝试获取,直到成功为止,这种循环获取的机制被称为自旋锁(spinlock)。
自旋锁的优点:
持有锁的线程在短时间内释放锁,那些等待竞争锁的线程就不需进入阻塞状态(无需线程上下文切换/无需用户态与内核态切换),它们只需要等一等(自旋),等到持有锁的线程释放锁之后即可获取,这样就避免了用户态和内核态的切换消耗。
自旋锁的缺点:
线程在长时间内持有锁,等待竞争锁的线程一直自旋,即CPU一直空转,资源浪费在毫无意义的地方,所以一般会限制自旋次数。
最后来说自旋锁的实现,实现自旋锁可以基于CAS实现,先定义lockValue对象默认值1,1代表锁资源空闲,0代表锁资源被占用,代码如下:
1 public class SpinLock { 2 3 //lockValue 默认值1 4 private AtomicInteger lockValue = new AtomicInteger(1); 5 6 //自旋获取锁 7 public void lock(){ 8 9 // 循环检测尝试获取锁 10 while (!tryLock()){ 11 // 空转 12 } 13 14 } 15 16 //获取锁 17 public boolean tryLock(){ 18 // 期望值1,更新值0,更新成功返回true,更新失败返回false 19 return lockValue.compareAndSet(1,0); 20 } 21 22 //释放锁 23 public void unLock(){ 24 if(!lockValue.compareAndSet(1,0)){ 25 throw new RuntimeException("释放锁失败"); 26 } 27 } 28 29 }
参考链接:https://segmentfault.com/a/1190000040042588
标签:Java,变量,CAS,编程,并发,线程,自旋,操作 From: https://www.cnblogs.com/hld123/p/18063098