java CAS 详解

什么是CAS?

  • CAS(Compare and swap),即比较并交换,原子性操作。

为什么需要CAS?

首先我们先开一份代码

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Demo01 {
    private static int count = 0;

    public static void request() throws InterruptedException {
        TimeUnit.MICROSECONDS.sleep(5);//休眠5毫秒

        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        int threadeSize=100;
        long startTime = System.currentTimeMillis();//记录开始时间
        CountDownLatch countDownLatch = new CountDownLatch(threadeSize);
        for(int i=0;i<threadeSize;i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10; i++) {
                            request();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown(); // 线程-1
                    }
                }
            });
            thread.start();
        }
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 耗时 "+(endTime-startTime)+" ms"+" 请求了 "+ count+"  次");
    }
}

代码中,设置了100个线程,每个线程对count 加10次 那么按照单线程的思想的话,结果应该是1000.
我们看下结果。
在这里插入图片描述
结果并不是1000,这是为什么呢??
== 因为 count++ 的操作不是原子性的==
根据jvm 执行引擎的规定 , count++ 分为3步 第一步 取出count的值记为Ra,第二步 将Ra的值 +1 记为Rb 第三步将Rb的值赋值给count。

怎么解决这个问题呢?

首先我们肯定知道有一个关键字 就是给对象加锁 让他们实现" 串行操作"。synchronized 关键字 会对对象加锁,每一次进入synchronized 的作用范围的对象只能有一个 ,只有当获取锁的对象释放锁了 才会让另外一个线程进入。

那么 我们将原来的代码改成这样

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Demo01 {
    private static int count = 0;

    public static synchronized void request() throws InterruptedException {
        TimeUnit.MICROSECONDS.sleep(5);//休眠5毫秒

        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        int threadeSize=100;
        long startTime = System.currentTimeMillis();//记录开始时间
        CountDownLatch countDownLatch = new CountDownLatch(threadeSize);
        for(int i=0;i<threadeSize;i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10; i++) {
                            request();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown(); // 线程-1
                    }
                }
            });
            thread.start();
        }
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 耗时 "+(endTime-startTime)+" ms"+" 请求了 "+ count+"  次");
    }
}

结果
在这里插入图片描述

  • 我们发现结果是正确了1000次,但是耗时1075ms 会不会太长了?如果是更多的线程 那么不是需要等待更久的时间的嘛。 这不就和获取第二天的时间 直接用 TimeUnit.HOURS.sleep(24); 一样的效果嘛。

优化代码

思路
  • 我们发现 count++ 是三步,第一步 取出count的值 我们没办法操作,第二步 count+1 我们也没办法操作,只有第三步,count = count+1 这个我们可以操作一下。

怎么实现CAS

CAS 不是 比较and 替换嘛 那我们就每次用getCount 和 理想中count应该是的值来比较,如果相等 就赋值新的 如果不等 就不操作。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class Demo03 {
    private static volatile int count = 0;
    // 增加volatile 关键字 保证可见性,每次getCount() 的值都是最新值。

    public static  void request() throws InterruptedException {
        TimeUnit.MICROSECONDS.sleep(5);//休眠5毫秒

        while (!compareAndSwap(getCount(),getCount()+1)); // 直到修改了 才退出循环

    }

    /**
     *
     * @param expectCount 期望值(期望count=多少)
     * @param newCount 新值 (重新将count设置为多少)
     * @return 成功返回True 失败返回False
     */
    public synchronized static Boolean compareAndSwap(int expectCount,int newCount){
        if(getCount() == expectCount ) {//如果get到的count 等于期望值 就赋值一个新的值
            count = newCount;
            return true;
        }
        return  false;
    }
    public static int getCount(){
        return count;
    }
    public static void main(String[] args) throws InterruptedException {
        int threadeSize=100;
        long startTime = System.currentTimeMillis();//记录开始时间
        CountDownLatch countDownLatch = new CountDownLatch(threadeSize);
        for(int i=0;i<threadeSize;i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for (int i = 0; i < 10; i++) {
                            request();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown(); // 线程-1
                    }
                }
            });
            thread.start();
        }
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 耗时 "+(endTime-startTime)+" ms"+" 请求了 "+ count+"  次");
    }
}

结果
在这里插入图片描述

总结

  • 比较和置换,这就是我们的CAS机制。 因为比较是原子性的,赋值也是原子性的。

扩展 (JAVA 中对CAS的实现 和 CAS的弊端)

java 中对CAS的支持

  • java 提供了CAS操作的支持,具体在sum.misc.unsafe 类中
@ForceInline
   public final boolean compareAndSwapObject(Object o, long offset,
                                             Object expected,
                                             Object x) {
       return theInternalUnsafe.compareAndSetObject(o, offset, expected, x);
   }

   /**
    * Atomically updates Java variable to {@code x} if it is currently
    * holding {@code expected}.
    *
    * <p>This operation has memory semantics of a {@code volatile} read
    * and write.  Corresponds to C11 atomic_compare_exchange_strong.
    *
    * @return {@code true} if successful
    */
   @ForceInline
   public final boolean compareAndSwapInt(Object o, long offset,
                                          int expected,
                                          int x) {
       return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
   }

   /**
    * Atomically updates Java variable to {@code x} if it is currently
    * holding {@code expected}.
    *
    * <p>This operation has memory semantics of a {@code volatile} read
    * and write.  Corresponds to C11 atomic_compare_exchange_strong.
    *
    * @return {@code true} if successful
    */
   @ForceInline
   public final boolean compareAndSwapLong(Object o, long offset,
                                           long expected,
                                           long x) {
       return theInternalUnsafe.compareAndSetLong(o, offset, expected, x);
   }

ABA 问题

什么是ABA问题

  • A线程获取到了期望值 也计算出了新值,但是时间片用完了,轮到B线程了,B线程也获的了期望值也计算出了新值,他将原来的count 改成了一个数N,紧接着又改回来了。这时轮到A线程去进行CAS操作,发现期望值和get到的值一直,就进行了 赋新值操作。根本没有发觉到count 的值改变过。

  • 代码

import java.util.concurrent.atomic.AtomicInteger;

public class CasABADemo01 {
    public static AtomicInteger a = new AtomicInteger();//Integer 的原子类
    public static void main(String[] args){
        Thread main = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作线程 "+ Thread.currentThread().getName()+" , 初始值 L: "+a.get());
                try {
                    int expectNum = a.get();
                    int newNum = expectNum + 1;
                    Thread.sleep(1000);//睡一下 让别人先执行
                    boolean result = a.compareAndSet(expectNum, newNum);//成功返回true  失败返回 false
                    System.out.println("操作线程 "+ Thread.currentThread().getName()+" , CAS操作  "+result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"主线程");
        Thread other = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(20);//确保main先执行
                    a.incrementAndGet();//a+1
                    System.out.println("操作线程 "+ Thread.currentThread().getName()+" ,increment 后的值: "+a.get());
                    a.decrementAndGet();//a-1
                    System.out.println("操作线程 "+ Thread.currentThread().getName()+" ,decrement 后的值: "+a.get());

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"干扰线程");
        main.start();
        other.start();
    }
}


  • 结果
    在这里插入图片描述

怎么解决

  • 增加一个版本号
  • 代码
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CasABADemo02 {
    public static AtomicStampedReference<Integer> a = new AtomicStampedReference(1,1);//Integer 的原子类
    public static void main(String[] args){
        Thread main = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作线程 "+ Thread.currentThread().getName()+" , 初始值 L: "+a.getReference());
                try {
                    int expectReference = a.getReference();
                    int newReference = expectReference + 1;
                    int expectStamp = a.getStamp();
                    int newStamp = expectStamp +1;
                    Thread.sleep(1000);//睡一下 让别人先执行
                    boolean result = a.compareAndSet(expectReference, newReference,expectStamp,newStamp);//成功返回true  失败返回 false
                    System.out.println("操作线程 "+ Thread.currentThread().getName()+" , CAS操作  "+result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"主线程");
        Thread other = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);//确保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 (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"干扰线程");
        main.start();
        other.start();
    }
}

  • 结果
    *在这里插入图片描述
    本文参考 https://www.bilibili.com/video/av94403695?p=1 小刘老师 (疯狂推荐 )
发布了18 篇原创文章 · 获赞 5 · 访问量 376

猜你喜欢

转载自blog.csdn.net/qq_41050869/article/details/104781738
今日推荐