并发之CAS的举例详解
CAS原理举例子通俗解释
CAS的应用场景:
无论是ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了CAS
java并发过程中,要做到线程安全的考虑:
1.sychronized:这种独占锁是采用悲观锁的方式去做的,多个线程去请求资源时会阻塞进行,以保证线程安全。
2.volatile关键字:它只能保证内存可见性不能保证原子性,而且对于有多个共享资源的情况下就无法去保证线程安全了,还是要采用sychronized去做。
3.cas是乐观锁的一种实现,就是不去加锁达到线程安全的目的,所谓乐观就是认为永远不会发生多个线程对资源的请求永远不会发生冲突,如果有冲突再去解决(一直重试,直到成功)。
cas的解释
我们举一个例子来说:拿基于cas实现的juc包下的一个可进行原子操作的原子类AtomicInteger来说:
jdk1.8.0_202\jre\lib\rt.jar!\java\util\concurrent\atomic\AtomicInteger.class
以上是它再jdk中的path
一、类的构成
由上面的源码可以看到几个关键点:
1.静态成员变量中获取到了Unsafe类的实例(这是CAS能够实现原子操作的核心类)
2.在静态代码块中通过反射获取到了当前对象中的value属性,并将他传入了unsafe的objectFieldOffset方法中(该方法是native方法)。static
块的加载发生于类加载的时候,是最先初始化的,这时候我们调用unsafe
的objectFieldOffset
从Atomic
类文件中获取value
的偏移量,那么valueOffset
其实就是记录value
的偏移量的(可以根据这个偏移量算出正真的内存地址)。
value就是这个AtomicInteger变量的值,它是由volatile关键字修饰的以此保证内存可见性,我们可以通过AtomicInteger类的get()方法获取到value的值。
二、取一个方法来说明CAS
AtomicInteger类中一个加法运算的方法的源码,对value进行加delta操作:
它调用了unsafe对象的getAndAddInt方法,传入的参数包括这个AtomicInteger对象本身,之前算好的偏移量(依靠它可以获得value真实的内存地址),以及要加的值。看一下unsafe的getAndAddInt方法:
这里假如A线程获取到Var5的最新值为10打算通过compareAndSwapInt()去修改值为11时,B线程先一步修改了内存中的value为11,这时候A通过compareAndSwapInt()方法先去传入的对象中根据偏移量取出内存中的最新值和之前取得Var5作比较**(这里的Var5就是CAS中所说的旧的预期值,内存中重新取得值就是内存中的值)**,此时肯定不相等,那么就返回false,让它进行自旋操作重新取值给Var5赋值,再进行compareAndSwapInt()方法。
这就是CAS的核心思想:每次获取内存中的值与旧的预期值作比较,如果不一致就重新去算旧的预期值,如果一致就做CAS(比较交换)操作来达到原子操作的目的。这里的比较交换并不是两个操作,它在native方法中是借助cpu指令来进行的所以一定是原子操作。
compareAndSwapInt()方法:一个native方法。
Unsafe类中的方法在其他的地方的用途
1.AQS框架中,在独占资源的情况下,多个线程去请求资源如果资源被占有就要把这些线程封装成node节点存放在一个CLH队列中,因为要按顺序去排队获取资源,在没有排到自己的时候就不要让线程一直运行了,这时候是通过unsafe的park()方法让这些线程进入到waiting状态的,
2.在面试中如果可以用unsafe的park和unpark方法来写出线程间的通信的编程题目是加分项。