原博客地址:https://blog.youkuaiyun.com/qq_37113604/article/details/81582784
1、什么是CAS?
CAS加volatile关键字是实现并发包的基石。没有CAS就不会有并发包,synchronized是一种独占锁、悲观锁,java.util.concurrent中借助了CAS指令实现了一种区别于synchronized的一种乐观锁。核心是冲突检测和数据更新
CAS操作包含三个操作数-内存位置、期望值、和新值,如果内存位置的值与期望值匹配
那么处理器会自动将该位置更新为新值,否则,处理器不作任何操作。
2、CAS的实现原理是什么?
CAS通过调用JNI的代码实现,JNI(允许java调用其他语言)
而compareAndSwapxxx系列的方法就是借助C语言来调用cpu底层指令实现的
最终映射到的cpu的指令为cmpxchg 这是一个原子指令 cpu执行此命令时,实现比较并替换的操作
代码模拟CAS的实现:
package cn.mrhan.java.cas;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @Author hanYu
* @Date 2021/8/23
* @Description
**/
public class CasDemo {
//总访问量
static volatile int count = 0 ;
//模拟访问的方法
public static void request() throws InterruptedException{
//模拟耗时5毫秒
TimeUnit.MILLISECONDS.sleep(5);
int expectCount;
while (!compareAndSwap((expectCount=getCount()),expectCount+1)){};
}
/**
* 此方法模拟CAS的实现原理 即当线程发现拿到的值和期待的值相等时,将拿到的值加1操作
* @param expectCount
* @param newCount
* @return
*/
public static synchronized boolean compareAndSwap(int expectCount,int newCount){
if(getCount()==expectCount){
count = newCount;
return true;
}
return false;
}
public static int getCount(){
return count;
}
public static void main(String[] args) throws InterruptedException {
//开始时间
long startTime = System.currentTimeMillis();
int threadSize = 100;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for(int i=0;i<threadSize;i++){
Thread thread = new Thread(()->{
try {
for(int j=0;j<10;j++){
request();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
});
thread.start();
}
//保证100个线程运行完之后再执行后面代码
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"耗时:"+(endTime-startTime)+"毫秒"+",总访问量count="+count);
}
}
3、cmpxchg怎么保证多核心下的线程安全?
系统底层进行CAS操作的时候,会判断当前系统是否为多线程系统,如果是,就给总线加锁
只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说CAS的原子行是平台级别的
4、CAS存在哪些问题
ABA问题、循环时间长开销大、只能保证一个共享变量的原子操作
5、ABA问题
CAS 需要在操作值的时候检查值有没有发生变化,如果没有发生变化则更新 但是如果一个值原来是A ,在CAS方法执行之前 呗其他线程修改还为了B 然后又修改回了A 那么CAS方法执行检查的时候会发现它的值没有发生变化 但是实际上却变化了
代码模拟ABA问题
package cn.mrhan.java.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author hanYu
* @Date 2021/8/23
* @Description
**/
/**
* CAS 需要在操作值的时候检查值有没有发生变化,如果没有发生变化则更新 但是如果一个值原来是A ,在CAS方法执行之前 呗其他线程修改还为了B 然后又修改回了A 那么CAS方法执行检查的时候会发现它的值没有发生变化 但是实际上却变化了
*
* 如何解决ABA问题?
* 解决ABA最简单的方案就是给值加一个修改版本号,每次值变化 都会修改它的版本号CAS操作时候都会去对比此版本号
* java中ABA解决方法
* AtomicStampedReference主要包含一个对象引用及一个可以自动更新的整数stamp的pair对象来解决ABA问题
*/
public class CASABADemo {
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);//让主线程休眠一秒钟 让出CPU
Boolean isSuccess = a.compareAndSet(expectNum,newNum);
System.out.println("操作线程"+Thread.currentThread().getName()+"CAS操作:"+isSuccess);
}catch (Exception e){
e.printStackTrace();
}
}
},"主线程");
Thread other = new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(20);//确保main线程优先执行
a.incrementAndGet();//a+1 a=2
System.out.println("操作线程"+Thread.currentThread().getName()+"[increment],值="+a.get());
a.decrementAndGet();//a-1 a=1
System.out.println("操作线程"+Thread.currentThread().getName()+"[decrement],值="+a.get());
}catch (Exception e){
e.printStackTrace();
}
}
},"干扰线程");
main.start();
other.start();
}
/**
* 运行结果:
* 操作线程主线程初始值:1
* 操作线程干扰线程[increment],值=2
* 操作线程干扰线程[decrement],值=1
* 操作线程主线程CAS操作:true
*/
}
6、如何解决ABA问题
解决ABA最简单的方案就是给值加一个修改版本号,每次值变化 都会修改它的版本号CAS操作时候都会去对比此版本号
java中ABA解决方法
AtomicStampedReference主要包含一个对象引用及一个可以自动更新的整数stamp的pair对象来解决ABA问题
代码示例
package cn.mrhan.java.cas;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @Author hanYu
* @Date 2021/8/23
* @Description
**/
public class CASABADemo2 {
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);//让主线程休眠一秒钟 让出CPU
Boolean isSuccess = a.compareAndSet(expectReference,newReference,expectStamp,newStamp);
System.out.println("操作线程"+Thread.currentThread().getName()+"CAS操作:"+isSuccess);
}catch (Exception e){
e.printStackTrace();
}
}
},"主线程");
Thread other = new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(20);//确保main线程优先执行
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();
}
/**
* 运行结果:
* 操作线程主线程初始值:1
* 操作线程干扰线程[increment],值=2
* 操作线程干扰线程[decrement],值=1
* 操作线程主线程CAS操作:false
* false说明主线程对干扰线程的操作是有感知的 即解决了ABA问题
*/
}