Java-CAS

作者: Android_Gleam | 来源:发表于2020-09-28 17:13 被阅读0次

    原子性操作

    原子即为不可再分的,原子操作即要么所有操作全部完成 要么全不完成
    用synchronized包围的代码块或方法就是原子操作。对于线程来讲,synchronized包围代码,只会全部完成,不会执行一半而中断。
    synchronized是一个很重的操作,如果执行代码很简单,例如i++,很多线程都阻塞在外面很划不来,为了解决这种问题,引出了CAS(Compare And Swap),比较并且交换。系统提供了很多原子变量,Atomic开头的变量都是实现了CAS,例如AtomicBoolean、AtomicInteger等。

    CAS

    CAS原理

    我们下面举个例子:
    假如我们现在有多个线程执行count++这个操作。

    CASCount.jpg
    1. 首先从内存中取出count的值。(假如这时是0)
    2. 然后进行累加操作。(count变为了1)
    3. 这时在从内存中取出count的值,如果与第一步取到的值相等,则将累加操作后的值写入内存,否则 说明有别的线程改过了,这时再重复第一步操作,直到完成赋值操作。

    总结:
    获取内存中的值 ,进行操作,再写入内存的时候,进行判断当前内存中的值是否与之前取出的值是否一致,不一致的话以内存中的新值,重新计算,反复执行(自旋,其实就是死循环),直到内存中的值没有在经过修改,才进行写入操作。

    这里就引出了两个概念:

    • 悲观错 先锁再操作(synchronized)
    • 乐观锁 先操作再判断是否进行修改

    CAS和synchronized性能比较:
    正常生产环境下CAS的效率是要高于synchronized,因为synchronized会阻塞线程,线程阻塞的时候会发生上下文切换(3-5ms),CAS执行指令的时间大概在0.6ns。
    高度竞争,特意设计的情况下synchronized会优于CAS。

    为什么有CAS还需要synchronized

    • ABA问题:
      假设当前有两个线程ThreadA和ThreadB,一个变量A
      ThreadA想把A修改为B,根据上面介绍的CAS原理,我们知道,修改的时候会判断A是否被修改过,用段伪代码表示也就是if(A==A) A = B
      ThreadB比ThreadA跑的快,ThreadA把A修改为C,然后又改回为A。
      可是ThreadA认为A没被修改过。

    解决办法:版本戳 要求每个线程修改值的时候 加入一个版本戳
    jdk中提供了相关的操作
    AtomicMarkReference(有没有变过) AtomicStampedReference(变了几次)

    public class UserAtomicMarkable {
        static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("red",0);
    
        public static void main(String[] args) throws InterruptedException {
            final int oldStamp = atomicStampedReference.getStamp();
            final String oldReference = atomicStampedReference.getReference();
            System.out.println(oldReference + "----------" + oldStamp);
    
             Thread  threadA = new Thread(){
                @Override
                public void run() {
                    super.run();
                    System.out.println(Thread.currentThread().getName() + "当前变量值 " + oldReference + "-当前版本戳 " + oldStamp );
                    atomicStampedReference.compareAndSet(oldReference,oldReference + "Java",oldStamp,oldStamp + 1);
                }
            };
    
            Thread  threadB = new Thread(){
                @Override
                public void run() {
                    super.run();
                    String reference = atomicStampedReference.getReference();
                    int stamp = atomicStampedReference.getStamp();
                    System.out.println(Thread.currentThread().getName() + "当前变量值 " + reference + "-当前版本戳 " + stamp );
                    atomicStampedReference.compareAndSet(reference,reference + "C",stamp,stamp + 1);
                    String reference1 = atomicStampedReference.getReference();
                    int stamp1 = atomicStampedReference.getStamp();
                    System.out.println(Thread.currentThread().getName() + "当前变量值 " + reference1 + "-当前版本戳 " + stamp1 );
                }
            };
    
            threadA.start();
            threadA.join();
            threadB.start();
            threadB.join();
    
            System.out.println(atomicStampedReference.getReference() + "----------" + atomicStampedReference.getStamp());
        }
    
    }
    
    • 开销问题
      当竞争激烈的时候,会存在长时间完成不了操作 ,造成自旋,一直重试,会占用CPU资源。
      解决办法:换成synchronized。

    • 只能保证一个共享变量的原子操作
      CAS操作时,只能针对某个内存地址上的值进行修改,而一个地址往往只能保存一个变量。
      解决办法:AtomicReference把多个变量打包到一个对象中 替换对象

    public class UseAtomicReference {
        static AtomicReference<User> atomicReference;
    
        public static void main(String[] args) {
            User user = new User("xiaoming",22);
            atomicReference = new AtomicReference<>(user);
            User updateUser = new User("xiaohong",16);
            atomicReference.compareAndSet(user,updateUser);
    
            System.out.println(atomicReference.get());
            System.out.println(user);
        }
    
        static class User{
            private String name;
            private int age;
    
            public User(String name, int age) {
                this.name = name;
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public int getAge() {
                return age;
            }
    
            @Override
            public String toString() {
                return "User{" +
                        "name='" + name + '\'' +
                        ", age=" + age +
                        '}';
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Java-CAS

          本文链接:https://www.haomeiwen.com/subject/lwpzsktx.html