文章目录
- 什么是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() {
@Override
public 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() {
@Override
public 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;
return
expectedReference == 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() {
@Override
public 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() {
@Override
public 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
就失败了。
文章参考:小刘老师讲源码