CAS概念和解析
作者:互联网
一、CAS概念
package syncbasics; import java.util.concurrent.CountDownLatch; /** * 多线程访问同一份数据,会产生竞争,race condition => 竞争条件 * 就有可能产生数据的不一致,并发访问之下产生的不期望出现的结果 * 如何保障数据的一致呢?---->线程同步(线程执行的顺序安排好), * 具体:保障操作的原子性(Atomicity) * 1.悲观的认为这个操作会被别的线程打断(悲观锁)synchronized * 2.乐观的认为这个操作不会被别的线程打断(乐观锁)cas操作 * CAS = Compare And Set/Swap/Exchange * * ++操作: * 把n从内存里面读到寄存器里,加完了之后,再写回去。 * 还没来得及写回去的时候,另外的线程读到了原值, * 因此,++这个正在执行的操作被另外线程打断了。 * * 只要我们能保证这个执行的操作不被打断,即保证我这个线程读过来之后改完这个值1,再写回去之后,其他线程才能执行, * 那最后的结果一定是对的。这种不能够被打断的操作称之为原子操作。 * * 什么样的语句是原子性的?什么样的不是? * java内存中的8大原子操作,了解即可。要查汇编手册。 * * synchronized: * 让原来的并发变成了序列化。 * synchronized本身是保证可见性的,n++结束了之后这个线程一定是要和主内存做同步,主内存里一定都是最新的。 * synchronized保障了可见性、原子性。 * 那么保证有序性吗? * ----不可以,synchronized里面的代码块里面的操作指令完全有可能换顺序。DCL要加volatile就是证明了。 * * CAS概念: * 还以下面这个小程序n++操作来说明CAS的概念。 * n开始等于0,线程1把n读过来加1,原来是需要加锁的,现在整个过程不上锁了,把0读过来改成1之后,再往回写 * 的过程之中做个判断,判断原值依然是否为0,如果依然为0,说明在线程1读0加1的过程中,没有人来过,那就直接把1 * 写回去,搞定。 * 万一中间有人改了呢?万一其他线程已经将0改成8了,线程1把1往回写的时候发现原值已经变为8了,不是你所期望的0, * 这时候怎么办?那就再来一遍,把8读出来加1变成9,把9往回写的过程之中看看判断原值是否依然为8,如依然为8,说明在我 * 将8改为9的过程之中没有其他人来过,那就直接将9写回去了,搞定。 * 当然,如果将8读来加1的过程之中又有人打断了,有人将8改成100了,怎么办?那就把100读过来加1,将101往回写的过程中判断 * 原值是否依然是100....一直到某一次成功了为止。 * 你会发现它就在这里不停的循环,读取当前值,计算结果,比较当前值和新值,如果当前值和新值相等,更新为新值。如果不相等, * 就再来一遍,总有一次能成功。 * * CAS的ABA问题: * 上面的这段话描述里面有个很重要的问题,此0非彼0的问题,线程1把0读过来改成了1,往回写的过程中发现原值依然为0,但是 * 这个0是不是你所看到的那个0呢?未必,有可能在这个过程之中,这个0被别的线程改成了8,又被别的线程改回了0!中间有个 * 0->8->0的过程,此0非彼0,A->B->A,这就是ABA问题。 * 但是咱们这个程序是不存在这个问题,只是理论上有,简单数据类型就算ABA问题,但是对我而言没关系,这种可以不在乎,略过。 * 但是在有些情况下,是要解决的。如果这个值是一个引用的话,读过来引用值,对它的属性进行了一些修改,它是一个对象,当你再 * 往回写的时候,有可能这个引用指向的对象里面的内容发生了改变,引用依然还是这个引用,但是里面的内容发生了改变,这时就要 * 在乎这个ABA问题了。 * * 解决ABA问题: * 加version,你的女朋友,你早上离开了她,晚上回来发现依然是她,但此她还是彼她嘛?你比较怀疑,怎么办?在她脑门上写1.0,你就 * 走了,回来的时候依然是她,不过如果中间经过任何其他线程操作,这个ver1.0都会加1。这时候你发现她脑门上是99.0,那肯定中间经过 * 了改变,当然就看你在乎不在乎。所以加version,加版本就可以解决。 * * CAS的底层原子性保障: * 除了ABA问题,CAS还有一个巨大问题。分析一下: * 线程1将0读过来改成1,把1往回写的过程中是一个CAS操作。CAS叫compare and swap,compare and set,伪代码就是: * if(v==0){v=1},这个操作实际上底层就是两步:比较和设定。那万一当你执行完if(v==0)后,这个时间点上被另外线程打断, * 另外线程把0改成8了,那线程1又将8->1,那还是出问题了,数据还是不一致! * 所以,如果想让CAS产生作用的话,必须保证CAS操作本身必须是原子性的。 * */ public class T00_IPlusPlus { private static long n = 0L; public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[100]; CountDownLatch latch = new CountDownLatch(threads.length); for(int i=0; i<threads.length; i++){ threads[i] = new Thread(() -> { for(int j=0; j<10000; j++){ synchronized (T00_IPlusPlus.class) { n++; } } latch.countDown(); }); } for(Thread t : threads){ t.start(); } latch.await(); System.out.println(n); } }
二、AtomicXXX类:
package atomicxxx; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * 这里就是一种CAS机制 * incrementAndGet每次往回写的时候都要比较一下,看下是否原值是我期望的那个值。 * incrementAndGet操作自带原子性: * unsafe.getAndAddInt->this.compareAndSwapInt->native boolean compareAndSwapInt * 得益于CPU在汇编级别上支持指令:cmpxchg,但是cmpxchg不是原子性的,最终实现: * lock cmpxchg * 所以你会发现,CAS在宏观上我们叫做自旋锁,乐观锁,但它在底层上的实现,微观上的实现实际上是一个悲观锁。 */ public class T01_AtomicInteger { AtomicInteger count = new AtomicInteger(0); void m(){ for(int i=0; i<10000; i++){ count.incrementAndGet(); } } public static void main(String[] args) { T01_AtomicInteger t = new T01_AtomicInteger(); List<Thread> threads = new ArrayList<>(); for(int i=0; i<100; i++){ threads.add(new Thread(t::m, "thread-" + i)); } threads.forEach(o -> o.start()); threads.forEach(o -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } }
---
标签:ABA,原值,读过来,CAS,概念,线程,操作,解析 来源: https://www.cnblogs.com/tenWood/p/16026043.html