Java并发基石_CAS原理实战02_CAS实现原理
文章目录
-
- 什么是CAS?
- CAS的实现原理是什么?
- cmpxchg指令怎么保证多核心下的线程安全?
- 什么是ABA问题?
- 如何解决ABA问题呢?
什么是CAS?
CAS
,全称CompareAndSwap
,比较并替换。
CAS
包含了三个操作数,内存位置值V
,期望值A
,新值B
,如果内存位置值V
与期望值A
匹配,处理器就将内存位置值更新为新值B
,否则不做任何操作。无论发生哪种情况,它都会在CAS
指令之前返回该位置的值。
sun.misc.Unsafe
类中提供了对CAS
操作的支持
/*** Atomically update Java variable to <tt>x</tt> if it is currently* holding <tt>expected</tt>.* @return <tt>true</tt> if successful*/public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);/*** Atomically update Java variable to <tt>x</tt> if it is currently* holding <tt>expected</tt>.* @return <tt>true</tt> if successful*/public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);/*** Atomically update Java variable to <tt>x</tt> if it is currently* holding <tt>expected</tt>.* @return <tt>true</tt> if successful*/public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
o:
表示要操作的对象offset:
表示要操作对象中属性地址的偏移量expected:
表示期望值x:
表示新值
CAS的实现原理是什么?
CAS
通过调用Java native interface
的代码实现,允许Java
调用其他语言,compareandswap
系列的方法就是借助c
语言调用cpu
底层指令(cmpxchg
)来实现的,cpu
执行该指令时就实现了比较并替换的操作。
cmpxchg指令怎么保证多核心下的线程安全?
系统底层进行CAS
操作的时候,首先会判断当前系统是否为多核,如果是就会对总线进行加锁,且只有一个线程可以可以对总线加锁成功,加锁成功之后就会执行CAS
操作。
什么是ABA问题?
CAS
操作需要在操作值的时候判断值有没有变化,如果没有变化则更新。但是如果一个值原来是A
,在CAS
方法执行之前,另一个线程先把A
的值修改为B
,然后又修改为A
,那么CAS
操作就会误认为A
的值没有发生过变化,但是实际上A
是发生过变化的。
代码模拟测试:
package juc.cas;import java.util.concurrent.atomic.AtomicInteger;public class ABADemo {
// 原子变量,是使用CAS实现的public static AtomicInteger a = new AtomicInteger(1);public static void main(String[] args) {Thread main = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("操作线程"+Thread.currentThread().getName()+",初始值"+a.get());try {int expectNum = a.get();int newNum = expectNum + 1;Thread.sleep(1000);boolean isCASSuccess = a.compareAndSet(expectNum,newNum);System.out.println("操作线程"+Thread.currentThread().getName()+",CAS操作"+isCASSuccess);}catch (Exception e){e.printStackTrace();}}},"主线程");Thread other = new Thread(new Runnable() {@Overridepublic void run() {try {
// 确保main线程先执行Thread.sleep(20);a.incrementAndGet();System.out.println("操作线程"+Thread.currentThread().getName()+",increment:"+a.get());a.decrementAndGet();System.out.println("操作线程"+Thread.currentThread().getName()+",decrement:"+a.get());}catch (Exception e){e.printStackTrace();}}},"干扰线程");main.start();other.start();}
}
可以看到,CAS
操作是成功了的。
如何解决ABA问题呢?
引入版本号机制,A
每被修改一次版本号的值就会+1
,当执行CAS
操作的时候,拿到期望值的版本号与当前值的版本号进行比对,如果一致,则执行CAS
操作。
图中的stamp
就是版本号。
* @param expectedReference the expected value of the reference//期望引用* @param newReference the new value for the reference//新值引用* @param expectedStamp the expected value of the stamp//期望引用的版本号* @param newStamp the new value for the stamp//新值引用的版本号* @return {@code true} if successful
public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));}
代码模拟测试:
package juc.cas;import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;public class ABADemo {
// 原子变量,是使用CAS实现的public static AtomicStampedReference<Integer> a = new AtomicStampedReference(new Integer(1),1);public static void main(String[] args) {Thread main = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("操作线程"+Thread.currentThread().getName()+",初始值"+a.getReference());try {Integer expectReference = a.getReference();Integer newReference = expectReference + 1;Integer expectStamp = a.getStamp();Integer newStamp = expectStamp+1;Thread.sleep(1000);boolean isCASSuccess = a.compareAndSet(expectReference,newReference,expectStamp,newStamp);System.out.println("操作线程"+Thread.currentThread().getName()+",CAS操作"+isCASSuccess);}catch (Exception e){e.printStackTrace();}}},"主线程");Thread other = new Thread(new Runnable() {@Overridepublic void run() {try {
// 确保main线程先执行Thread.sleep(20);a.compareAndSet(a.getReference(),a.getReference()+1,a.getStamp(),a.getStamp()+1);System.out.println("操作线程"+Thread.currentThread().getName()+",increment:"+a.getReference());a.compareAndSet(a.getReference(),a.getReference()-1,a.getStamp(),a.getStamp()-1);System.out.println("操作线程"+Thread.currentThread().getName()+",decrement:"+a.getReference());}catch (Exception e){e.printStackTrace();}}},"干扰线程");main.start();other.start();}
}
引入版本号之后,CAS
就失败了。
文章参考:小刘老师讲源码