小滴课堂-学习笔记:必考知识点 并发编程进阶系列

logo 愿景:"让编程不再难学,让技术与生活更加有趣"


更多架构课程请访问 xdclass.net

目录

第1集 并发编程三要素你是否知道,解释+例子

第2集 常见进程/线程调度算法你是否知道

第3集 java多线程里面常用的锁找你知道多少《上》

第4集 java多线程里面常用的锁找你知道多少《下》

第5集 上机实战之多线程里面的死锁,写一个例子并解决死锁

第6集 上机实战之多线程里面的不可重入锁设计

第7集 上机实战之多线程里面的可重入锁设计

第8集 多线程的synchronized了解不,新版JDK里面优化点

第9集 高性能的Compare and Swap 你知道多少

第10集 CAS性能虽好, 但存在的ABA问题你知道不?

干货文档


第1集 并发编程三要素你是否知道,解释+例子

简介: 常见的并发编程三要素

  • 考点:考查是否知道并发编程3要素

  • 难度:【 ** **】

  • 并发编程三要素是否知道,能否分别解释下,举个简单的例子

    
    
    
    原子性:一个不可再被分割的颗粒,原子性指的是一个或多个操作要么全部执行成功要么全部执行失败,期间不能被中断,也不存在上下文切换,线程切换会带来原子性的问题
    ​
    int num = 1; // 原子操作
    num++; // 非原子操作,从主内存读取num到线程工作内存,进行 +1,再把num写到主内存, 除非用原子类,即java.util.concurrent.atomic里的原子变量类
    ​
    解决办法是可以用synchronized 或 Lock(比如ReentrantLock) 来把这个多步操作“变成”原子操作,但是volatile,前面有说到不能修饰有依赖值的情况
    ​
    public class XdTest {
        private int num = 0;
        
        //使用lock,每个对象都是有锁,只有获得这个锁才可以进行对应的操作
        Lock lock = new ReentrantLock();
        public  void add1(){
            lock.lock();
            try {
                num++;
            }finally {
                lock.unlock();
            }
        }
        
        //使用synchronized,和上述是一个操作,这个是保证方法被锁住而已,上述的是代码块被锁住
        public synchronized void add2(){
            num++;
        }
    }
    解决核心思想:把一个方法或者代码块看做一个整体,保证是一个不可分割的整体
    ​
    ​
    ​
    ​
    ========================================
    有序性: 程序执行的顺序按照代码的先后顺序执行,因为处理器可能会对指令进行重排序
    JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序结果的前提)
    int a = 3 //1
    int b = 4 //2
    int c =5 //3 
    int h = a*b*c //4
    ​
    上面的例子 执行顺序1,2,3,4 和 2,1,3,4 结果都是一样,指令重排序可以提高执行效率,但是多线程上可能会影响结果
    ​
    假如下面的场景,正常是顺序处理
    //线程1
    before();//处理初始化工作,处理完成后才可以正式运行下面的run方法
    flag = true; //标记资源处理好了,如果资源没处理好,此时程序就可能出现问题
    //线程2
    while(flag){
        run(); //核心业务代码
    }
    ​
    指令重排序后,导致顺序换了,程序出现问题,且难排查
    ​
    //线程1
    flag = true; //标记资源处理好了,如果资源没处理好,此时程序就可能出现问题
    //线程2
    while(flag){
        run(); //核心业务代码
    }
    before();//处理初始化工作,处理完成后才可以正式运行下面的run方法
    ​
    ​
    ​
    ========================================
    可见性: 一个线程A对共享变量的修改,另一个线程B能够立刻看到
    // 线程 A 执行
    int num = 0;
    // 线程 A 执行
    num++;
    // 线程 B 执行
    System.out.print("num的值:" + num);
    ​
    线程A执行 i++ 后再执行线程 B,线程 B可能有2个结果,可能是0和1。
    ​
    因为 i++ 在线程A中执行运算,并没有立刻更新到主内存当中,而线程B就去主内存当中读取并打印,此时打印的就是0;也可能线程A执行完成更新到主内存了,线程B的值是1。
    所以需要保证线程的可见性
    synchronized、lock和volatile能够保证线程可见性
    ​
    
    
    
    
    
    
    

     

 

 

第2集 常见进程/线程调度算法你是否知道

简介: 常见的进程、线程间调度算法

  • 考点:考查是否知道常见进程和线程间的调度算法

  • 难度:【 ** **】

  • 说下你知道的调度算法,比如进程间的调度;

    小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第1张图片

    
    
    
    先来先服务调度算法:
        按照作业/进程到达的先后顺序进行调度 ,即:优先考虑在系统中等待时间最长的作业
        排在长进程后的短进程的等待时间长,不利于短作业/进程
    ​
    短作业优先调度算法:
        短进程/作业(要求服务时间最短)在实际情况中占有很大比例,为了使得它们优先执行
        对长作业不友好
    ​
    高响应比优先调度算法: 
        在每次调度时,先计算各个作业的优先权:优先权=响应比=(等待时间+要求服务时间)/要求服务时间,
        因为等待时间与服务时间之和就是系统对该作业的响应时间,所以 优先权=响应比=响应时间/要求服务时间,选    择优先权高的进行服务需要计算优先权信息,增加了系统的开销
    
    ​
    时间片轮转调度算法:
        轮流的为各个进程服务,让每个进程在一定时间间隔内都可以得到响应
        由于高频率的进程切换,会增加了开销,且不区分任务的紧急程度
    ​
    ​
    优先级调度算法:
        根据任务的紧急程度进行调度,高优先级的先处理,低优先级的慢处理
        如果高优先级任务很多且持续产生,那低优先级的就可能很慢才被处理
    ​

     

小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第2张图片

 

  • 常见的线程间的调度算法是怎样的,java是哪种

    
    
    
    线程调度是指系统为线程分配CPU使用权的过程,主要分两种
    ​
    协同式线程调度(分时调度模式):线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里
    ​
    ​
    抢占式线程调度:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞
    ​
    ​
    Java线程调度就是抢占式调度,优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那就随机选择一个线程
    ​
    所以我们如果希望某些线程多分配一些时间,给一些线程少分配一些时间,可以通过设置线程优先级来完成。
    JAVA的线程的优先级,以1到10的整数指定。当多个线程可以运行时,VM一般会运行最高优先级的线程(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY)
    ​
    在两线程同时处于就绪runnable状态时,优先级越高的线程越容易被系统选择执行。但是优先级并不是100%可以获得,只不过是机会更大而已。
    ​
    有人会说,wait,notify不就是线程本身控制吗?
    其实不是,wait是可以让出执行时间,notify后无法获取执行时间,随机等待队列里面获取而已
    ​
    
    
    
    
    
    
    
    
    

     

 

第3集 java多线程里面常用的锁找你知道多少《上》

  • 考点:考查对常见的锁是否掌握

  • 难度:【 ** **】

  • 你日常开发里面用过java里面有哪些锁?分别解释下

    
    
    
    悲观锁:当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized
    乐观锁:每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,CAS不会保证线程同步,乐观的认为在数据更新期间没有其他线程影响
    小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁多
    ​
    ​
    公平锁:指多个线程按照申请锁的顺序来获取锁,简单来说 如果一个线程组里,能保证每个线程都能拿到锁 比如ReentrantLock(底层是同步队列FIFO:First Input First Output来实现)
    非公平锁:获取锁的方式是随机获取的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock
    小结:非公平锁性能高于公平锁,更能重复利用CPU的时间
    ​
    ​
    可重入锁:也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
    不可重入锁:若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
    小结:可重入锁能一定程度的避免死锁 synchronized、ReentrantLock 重入锁
    ​
        private void meathA(){
                //获取锁 TODO
            meathB();
        }
    ​
        private void meathB(){
                //获取锁 TODO
                //其他操作
        }
    ​
    ​
    ​
    自旋锁:一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,任何时刻最多只能有一个执行单元获得锁.
    小结:不会发生线程状态的切换,一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU
    常见的自旋锁:TicketLock,CLHLock,MSCLock

小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第3张图片

 

 

 

 

 

 

第4集 java多线程里面常用的锁找你知道多少《下》

  • 考点:考查对常见的锁是否掌握
  • 难度:【 ** **】
  • 你日常开发里面用过java里面有哪些锁?分别解释下



​
共享锁:也叫S锁/读锁,能查看但无法修改和删除的一种数据锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享
​
互斥锁:也叫X锁/排它锁/写锁/独占锁/独享锁/ 该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁。例子:如果 线程A 对 data1 加上排他锁后,则其他线程不能再对 data1 加任何类型的锁,获得互斥锁的线程即能读数据又能修改数据
​
死锁:两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法让程序进行下去
​
下面三种是Jvm为了提高锁的获取与释放效率而做的优化 针对Synchronized的锁升级,锁的状态是通过对象监视器在对象头中的字段来表明,是不可逆的过程,
偏向锁:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,获取锁的代价更低,
轻量级锁:当锁是偏向锁的时候,被其他线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,但不会阻塞,且性能会高点
重量级锁:当锁为轻量级锁的时候,其他线程虽然是自旋,但自旋不会一直循环下去,当自旋一定次数的时候且还没有获取到锁,就会进入阻塞,该锁升级为重量级锁,重量级锁会让其他申请的线程进入阻塞,性能也会降低
​
分段锁、行锁、表锁

小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第4张图片

 

 

 

 

 

 

 

第5集 上机实战之多线程里面的死锁,写一个例子并解决死锁

  • 考点:考查对常见的死锁是否掌握
  • 难度:【 ** ** * 】
  • 上机实战:写个多线程死锁的例子



线程在获得了锁A并且没有释放的情况下去申请锁B,
这时另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A
因此闭环发生,陷入死锁循环




​
/**
 * 小滴课堂
 * 面试专题第一季
 *
 */
public class DeadLockDemo {
​
    private static String locka = "locka";
​
    private static String lockb = "lockb";
​
    public void methodA(){
​
        synchronized (locka){
            System.out.println("我是A方法中获得了锁A "+Thread.currentThread().getName() );
​
            //让出CPU执行权,不释放锁
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            synchronized(lockb){
                System.out.println("我是A方法中获得了锁B "+Thread.currentThread().getName() );
            }
        }
​
    }
​
​
    public void methodB(){
        synchronized (lockb){
            System.out.println("我是B方法中获得了锁B "+Thread.currentThread().getName() );
​
            //让出CPU执行权,不释放锁
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            synchronized(locka){
                System.out.println("我是B方法中获得了锁A "+Thread.currentThread().getName() );
            }
        }
​
    }
​
​
    public static void main(String [] args){
​
        System.out.println("主线程运行开始运行:"+Thread.currentThread().getName());
​
        DeadLockDemo deadLockDemo = new DeadLockDemo();
​
        new Thread(()->{
            deadLockDemo.methodA();
        }).start();
​
        new Thread(()->{
            deadLockDemo.methodB();
        }).start();
​
        System.out.println("主线程运行结束:"+Thread.currentThread().getName());
​
    }
​
}
​
  • 线上经常会产生诡异的情况,有可能是死锁,且难排查!!!

    
    
    
    死锁的4个必要条件
    ​
    互斥条件:资源不能共享,只能由一个线程使用
    请求与保持条件:线程已经获得一些资源,但因请求其他资源发生阻塞,对已经获得的资源保持不释放
    不可抢占:有些资源是不可强占的,当某个线程获得这个资源后,系统不能强行回收,只能由线程使用完自己释放
    循环等待条件:多个线程形成环形链,每个都占用对方申请的下个资源
    ​
    只要发生死锁,上面的条件都成立;只要一个不满足,就不会发生死锁
    

     

  • 那上面的例子怎么解决死锁,优化下代码



常见的解决办法:
    调整申请锁的范围
    调整申请锁的顺序
​
/**
 * 小滴课堂 面试专题第一季
 * 解决 死锁,通过调整锁的范围
 */
public class FixDeadLockDemo {
​
    private static String locka = "locka";
​
    private static String lockb = "lockb";
​
    public void methodA(){
​
        synchronized (locka){
            System.out.println("我是A方法中获得了锁A "+Thread.currentThread().getName() );
​
            //让出CPU执行权,不释放锁
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
        }
​
        synchronized(lockb){
            System.out.println("我是A方法中获得了锁B "+Thread.currentThread().getName() );
        }
    }
​
​
    public void methodB(){
        synchronized (lockb){
            System.out.println("我是B方法中获得了锁B "+Thread.currentThread().getName() );
​
            //让出CPU执行权,不释放锁
            try {
                Thread.sleep(2000);
​
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
        }
​
        synchronized(locka){
            System.out.println("我是B方法中获得了锁A "+Thread.currentThread().getName() );
        }
    }
​
​
    public static void main(String [] args){
​
        System.out.println("主线程运行开始运行:"+Thread.currentThread().getName());
​
        FixDeadLockDemo deadLockDemo = new FixDeadLockDemo();
​
​
        for(int i=0; i<10;i++){
            new Thread(()->{
                deadLockDemo.methodA();
            }).start();
​
            new Thread(()->{
                deadLockDemo.methodB();
            }).start();
        }
​
        System.out.println("主线程运行结束:"+Thread.currentThread().getName());
​
    }
​
}




第6集 上机实战之多线程里面的不可重入锁设计

  • 考点:考查对常见的不可重入锁是否掌握
  • 难度:【 ** ** 】
  • 上机实战:设计一个简单的不可重入锁



不可重入锁:若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
​
    private void methodA(){
            //获取锁 TODO
        methodB();
    }
​
    private void methodB(){
            //获取锁 TODO
            //其他操作
    }




/**
 * 小滴课堂:二当家小D
 *
 * 不可重入锁 简单例子
 *
 *  不可重入锁:若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
 */
public class UnreentrantLock {
​
    private boolean isLocked = false;
​
    public synchronized void lock() throws InterruptedException {
​
        System.out.println("进入lock加锁 "+Thread.currentThread().getName());
​
        //判断是否已经被锁,如果被锁则当前请求的线程进行等待
        while (isLocked){
            System.out.println("进入wait等待 "+Thread.currentThread().getName());
            wait();
        }
        //进行加锁
        isLocked = true;
    }
    public synchronized void unlock(){
        System.out.println("进入unlock解锁 "+Thread.currentThread().getName());
        isLocked = false;
        //唤醒对象锁池里面的一个线程
        notify();
    }
}
​
​
​
public class Main {
    private UnreentrantLock unreentrantLock = new UnreentrantLock();
    //加锁建议在try里面,解锁建议在finally
    public void  methodA(){
        try {
            unreentrantLock.lock();
            System.out.println("methodA方法被调用");
            methodB();
        }catch (InterruptedException e){
            e.fillInStackTrace();
        } finally {
            unreentrantLock.unlock();
        }
    }
​
    public void methodB(){
        try {
            unreentrantLock.lock();
            System.out.println("methodB方法被调用");
        }catch (InterruptedException e){
            e.fillInStackTrace();
        } finally {
            unreentrantLock.unlock();
        }
    }
    public static void main(String [] args){
        //演示的是同个线程
        new Main().methodA();
    }
}
​
//同一个线程,重复获取锁失败,形成死锁,这个就是不可重入锁

 

第7集 上机实战之多线程里面的可重入锁设计

  • 考点:考查对常见的重入锁是否掌握
  • 难度:【 ** ** * 】
  • 上机实战:设计一个简单的可重入锁



可重入锁:也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
​
​
/**
 * 小滴课堂:二当家小D
 *
 * 可重入锁 简单例子
 *
 *  不可重入锁:也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
 */
public class ReentrantLock {
​
    private boolean isLocked = false;
​
    //用于记录是不是重入的线程
    private Thread lockedOwner = null;
​
    //累计加锁次数,加锁一次累加1,解锁一次减少1
    private int lockedCount = 0;
​
    public synchronized void lock() throws InterruptedException {
​
        System.out.println("进入lock加锁 "+Thread.currentThread().getName());
​
        Thread thread = Thread.currentThread();
​
        //判断是否是同个线程获取锁, 引用地址的比较
        while (isLocked && lockedOwner != thread ){
            System.out.println("进入wait等待 "+Thread.currentThread().getName());
            System.out.println("当前锁状态 isLocked = "+isLocked);
            System.out.println("当前count数量 lockedCount =  "+lockedCount);
            wait();
        }
​
        //进行加锁
        isLocked = true;
        lockedOwner = thread;
        lockedCount++;
    }
    public synchronized void unlock(){
        System.out.println("进入unlock解锁 "+Thread.currentThread().getName());
​
        Thread thread = Thread.currentThread();
​
        //线程A加的锁,只能由线程A解锁,其他线程B不能解锁
        if(thread == this.lockedOwner){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                lockedOwner = null;
                //唤醒对象锁池里面的一个线程
                notify();
            }
        }
    }
}
​
​
​
​
public class Main {
    //private UnreentrantLock unreentrantLock = new UnreentrantLock();
    private ReentrantLock reentrantLock = new ReentrantLock();
​
    //加锁建议在try里面,解锁建议在finally
    public void  methodA(){
​
        try {
            reentrantLock.lock();
            System.out.println("methodA方法被调用");
            methodB();
​
        }catch (InterruptedException e){
            e.fillInStackTrace();
​
        } finally {
            reentrantLock.unlock();
        }
​
    }
​
    public void methodB(){
​
        try {
            reentrantLock.lock();
            System.out.println("methodB方法被调用");
​
        }catch (InterruptedException e){
            e.fillInStackTrace();
​
        } finally {
            reentrantLock.unlock();
        }
    }
​
    public static void main(String [] args){
        for(int i=0 ;i<10;i++){
            //演示的是同个线程
            new Main().methodA();
        }
    }
}
​








第8集 多线程的synchronized了解不,新版JDK里面优化点

  • 考点:考查对常见的synchronized是否掌握,新版JDK6里面优化了什么
  • 难度:【 ** **】
  • 对synchronized了解不,能否介绍下你对synchronized的理解



synchronized是解决线程安全的问题,常用在 同步普通方法、静态方法、代码块 中
​
非公平、可重入
​
每个对象有一个锁和一个等待队列,锁只能被一个线程持有,其他需要锁的线程需要阻塞等待。锁被释放后,对象会从队列中取出一个并唤醒,唤醒哪个线程是不确定的,不保证公平性
​
两种形式:
方法:生成的字节码文件中会多一个 ACC_SYNCHRONIZED 标志位,当一个线程访问方法时,会去检查是否存在ACC_SYNCHRONIZED标识,如果存在,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象,也叫隐式同步
​
代码块:加了 synchronized 关键字的代码段,生成的字节码文件会多出 monitorenter 和 monitorexit 两条指令,每个monitor维护着一个记录着拥有次数的计数器, 未被拥有的monitor的该计数器为0,当一个线程获执行monitorenter后,该计数器自增1;当同一个线程执行monitorexit指令的时候,计数器再自减1。当计数器为0的时候,monitor将被释放.也叫显式同步
​
两种本质上没有区别,底层都是通过monitor来实现同步, 只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成
​
​




查看字节码 
javac XXX.java
javap -v XXX.class
  • 同步方法字节码

    小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第5张图片

  • 同步代码块字节码

小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第6张图片

  • jdk1.6后进行了优化,你知道哪些大的变化



有得到锁的资源进入Block状态,涉及到操作系统用户模式和内核模式的切换,代价比较高
jdk6进行了优化,增加了从偏向锁到轻量级锁再到重量级锁的过渡,但是在最终转变为重量级锁之后,性能仍然较低

小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第7张图片

 

 

 

 

 

 

 

 

第9集 高性能的Compare and Swap 你知道多少

简介:并发编程面试核心点CAS考查

  • 考点:考查对常见的CAS是否掌握
  • 难度:【 ** ** 】
  • 了解CAS不,能否解释下什么是CAS

    
    
    
    全称是Compare And Swap,即比较再交换,是实现并发应用到的一种技术
    底层通过Unsafe类实现原子性操作操作包含三个操作数 —— 内存地址(V)、预期原值(A)和新值(B)。 
    如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 ,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
    ​
    CAS这个是属于乐观锁,性能较悲观锁有很大的提高
    AtomicXXX 等原子类底层就是CAS实现,一定程度比synchonized好,因为后者是悲观锁

     

image-20200212185212328

 

 

 

 

 

 

第10集 CAS性能虽好, 但存在的ABA问题你知道不?

简介:并发编程面试核心点CAS常见存在的问题考查

  • 考点:考查对CAS常见的问题是否掌握
  • 难度:【 ** ** 】
  • CAS会存在什么比较严重的问题?

    
    
    
    1、自旋时间长CPU利用率增加,CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用
    ​
    2、存在ABA问题 
    ​
    ​

     

 

  • 能否解释下什么是ABA问题,怎么避免这个问题呢?

     
    
    
    如果一个变量V初次读取是A值,并且在准备赋值的时候也是A值,那就能说明A值没有被修改过吗?其实是不能的,因为变量V可能被其他线程改回A值,结果就是会导致CAS操作误认为从来没被修改过,从而赋值给V
    ​
    给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值 还需要比较当前变量的版本号。
    在java5中,已经提供了AtomicStampedReference来解决问题,检查当前引用是否等于预期引用,其次检查当前标志是否等于预期标志,如果都相等就会以原子的方式将引用和标志都设置为新值

     

小滴课堂-学习笔记:必考知识点 并发编程进阶系列_第8张图片

 

 

 

 

干货文档

 

 

你可能感兴趣的:(面试,JAVA,并发编程,编程语言,多线程,java,面试,锁)