likes
comments
collection
share

如何应对Android面试官->CAS基本原理

作者站长头像
站长
· 阅读数 4

基本原理

CAS基本原理(Compare And Swap) 利用了现代处理器都支持 CAS 指令,循环这个指令,直到成功为止;

什么是原子操作?如何实现原子操作?

原子操作

要么全部完成,要么全部都不完成的操作;

例如:synchronized 关键字包裹的方法或者代码块就称为原子操作,但是因为这个关键字太重了,所以 JDK 就提出了 CAS;

代码示例:

public void synchornized add() {
    i++;
}

如果只为了实现一个 i++ 的操作,就是用 synchornized 的话,太重了,所以JDK 提出  CAS 操作;

如何实现原子操作

如何应对Android面试官->CAS基本原理

compare and  swap 比较并且交换,包含了至少两步,现代 CPU 中,都提供了单独的指令(CAS指令),由 CPU 来保证这两个操作一定是原子操作,要么全部完成,要么全不完成;

原理解释

如何应对Android面试官->CAS基本原理

假设有四个线程都要执行 count ++ 的操作,count 初始值为 0 ,首先这四个线程先将 count 值取到自己的内部,四个线程在自己的内部进行累加操作,也就是说把 count 由 0 变成了 1,接下来这四个线程都想把 count = 1 的值重新写回内存中去,那么四个线程同时写肯定是有问题的,所以 JDK 内部就使用了现代 CPU 所支持的这个 CAS 指令,首先只有一个线程能够执行这个指令(CPU保证),然后进行比较,比较内存中 count 的值是不是等于 0 ,因为当前这个线程在执行 count++ 的时候,是以 count = 0 为基准的,如果是 0,那么就把这个值 swap 成 1,执行完成之后退出,接着第二个线程进来,然后进行比较,比较内存中 count 的值是不是等于 0 ,因为这个线程在执行 count++ 的时候,也是以 count = 0 为基准的,但是此时 count 显然已经不是 0 了,已经被前一个线程给 swap 成 1 了,于是当前这个线程就会把 count = 1 再重新取一次,以 count = 1 进行 count++ 操作,然后进行比较,比较内存中 count 的值是不是等于 1,如果是 1,就把这个值 swap 成 2,依次类推,其他的线程也是执行这样的操作,直到这四个线程全部执行结束;

这里牵涉到了两个比较重要的概念

悲观锁 和 乐观锁

悲观锁:synchronized 关键字就是一个悲观锁,为什么?因为它在执行的时候总认为有人要改它的东西,那么它就先下手,先把这个锁抢到了,然后安安心心执行自己的操作;

乐观锁:在进行操作之前,没有其他人改我的东西,如果有人改了,那么我就重新来一次;

原子变量的操作性能要比锁的性能高;因为 synchornized 关键字在多个线程操作的时候只有一个线程能执行,其他线程就会被阻塞,一旦被阻塞了,就会发生上下文切换,而上下文的切换比较耗费性能(一次上下文的切换要耗费 3-5 us);现代 CPU 在执行一条 CAS 的指令大概在 0.6ns,虽然可能一直在那里自旋操作,但是也比一次上下文切换耗费的时间要少,而且当前线程在一次拿锁释放锁要发生两次上下文切换,这个时间(3-5us)是要 *2 的;而 CAS 中线程是不会进入这种阻塞状态的,它会不断的进行重试;

那么 CAS 既然这么强大,JDK 为什么没有完全采用 CAS?

CAS带来的问题

  1. ABA 问题
  • 假设有两个线程,线程1 想把变量由 A 变成 B,线程1 在执行操作的过程中,结果 线程 2 比线程 1 要快一步的把 变量 由 A 变成 B 再快速变回了 A,当线程1 执行 swap 操作的时候,发现它还是 A,执行了 swap 操作;这就是 ABA 问题,如果不关心这个问题的话,问题也不大,也是CAS指令与生俱来的机制带来的问题;
  1. 开销问题
  • 自旋操作,长期不成功,对于 CPU 来说开销还是比较大的;
  1. 只能保证一个共享变量的原子操作
  • 原子操作针对的是内存中的一个变量,而不能同时操作三个变量的交换;

原子变量类

JDK 中所有以 Atomic 开头的类都被称为原子变量类;

如何应对Android面试官->CAS基本原理

  1.  更新基本类型类
  • AtomicBoolean

  • AtomicInteger

  • AtomicLong

  1. 更新数组类
  • AtomicIntegerArray

  • AtomicLongArray

  • AtomicReferenceArray

  1. 更新引用类型类
  • AtomicReference

  • 用来解决 只能保证一个共享变量的原子操作 的问题

  • AtomicMarkableReference 

  • 带有版本戳的原子类,用来解决ABA的问题,只关心这个标记版本戳的变量有没有变过;

  • AtomicStampedReference

  • 带有版本戳的原子类,用来解决ABA的问题,不但关心这个比较版本戳的变量有没有变过,还关心变动过几次;

基本使用

AtomicInteger的基本使用

public class UseAtomic {    
    static AtomicInteger atomicInteger = new AtomicInteger(10);        
    public static void main(String[] args) {        
        // 这两个就是 i++ 和 ++i 的操作        
        atomicInteger.getAndIncrement();        
        atomicInteger.incrementAndGet();                
        atomicInteger.addAndGet(24);    
    }
}

AtomicReference的基本使用

public class UseAtomicReference {    
    static AtomicReference<UserInfo> atomicReference;        
    public static void main(String[] args) {        
        UserInfo userInfo = new UserInfo("Kobe", 42);        
        atomicReference = new AtomicReference<>(userInfo);        
        UserInfo jameInfo = new UserInfo("James", 37);        
        atomicReference.compareAndSet(userInfo, jameInfo);                
        System.out.println(atomicReference.get());    
    }        
    
    static class UserInfo {                
        private volatile  String name;        
        private int age;        
        public UserInfo(String name, int age) {            
            this.name = name;            
            this.age = age;        
        }        

        public String getName() {            
            return name;        
        }        
        
        public void setName(String name) {            
            this.name = name;        
        }        
        public int getAge() {            
            return age;        
        }        
        
        public void setAge(int age) {            
            this.age = age;        
        }        
        @Override        
        public String toString() {            
            return "UserInfo{" +                    
                "name='" + name + '\'' +                    
                ", age=" + age +                    
            '}';        
        }    
    }
}

简历润色

简历上可写:深度理解Java多线程、线程安全、并发编程、CAS原理、可手写ThreadLocal核心实现;

下一章预告

带你玩转阻塞队列和线程池原理;

欢迎三连

来都来了,点个关注、点个赞吧~~