代码仓库:https://github.com/Fiziluuk/JucDemo

  1. 谈谈volatile的理解

    1. volatile是java虚拟机提供的轻量级同步机制
      1. 保证可见性
      2. 不保证原子性
      3. 禁止指令重排(内存屏障:写的时候在写操作后面加入store屏障命令 读操作前加入load屏障命令)
    2. 谈谈java内存模型(JMM 不真实存在,是一种规范)
      1. 可见性:线程只能在自己的工作内存中修改数据,数据是从主内存拷贝过来的,不同的线程不能访问其他线程的工作内存。某一线程修改完自己工作内存的数据写回主内存后,需要通知其他线程,该数据已经改变

      2. 原子性(代码验证)

        代码验证

      3. 有序性

    3. 在那些地方用到过 volatile ?
      1. 单例模式 DCL 代码

        代码验证

      2. 单例模式 volatile 分析

  2. 谈谈CAS compareAndSet

    1. 比较并交换

    2. Unsafe类在sun.misc包中,调native方法像指针一样操作内存 unsafe.getAndAddInt(this, valueOffSet, 1) valueOffset是变量的内存偏移量

      /*
      var1 AtomicInteger对象本身
      var2 该对象值的引用地址
      var4 需要变动的数量
      var5 通过var1和var2找到主存中的对象的属性的真实值
      用该对象属性当前的值和var5进行比较
      如果相同,更新var5 + var4 并且返回true
      如果不同,循环继续取var5再比较,直到更新完成
      */
      AtomicInteger.getAndIncrement(){
          return unsafe.getAndAddInt(this, valueOffset, 1);
      }
      //unsafe.getAndAddInt
      pulic final int getAndAddInt(Object var1, long var2, int var4){
          int var5;
          do{
              var5 = this.getIntVolatile(var1, var2);
          }while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4);
          return var5;
      }
      
    3. CAS的底层原理是什么?如果知道,谈谈Unsafe

      1. CAS 操作是CPU原语,不能被打断
      2. CAS是什么?
        1. 比较当前工作内存中的值和主内存中的值,如果相同则执行相关操作,否则重新获取主存的值进行比较,直到主存和工作内存的值一致为止。(自旋)
        2. CAS应用:CAS有3个操作数,内存地址v, 工作内存值A, 修改后的值B,当且仅当a = v时,将内存的v修改为b,否则自旋比较
    4. CAS 缺点是什么?

      1. 循环时间长,开销大,造成 CPU 占用高。
      2. 只能保证一个共享变量的原子操作。
      3. 引出 ABA 问题
  3. 原子类 AtomicInteger 的 ABA 问题了解嘛?原子更新引用知道嘛?

    1. ABA 问题:CAS 算法实现的前提时从主存取数据并在当下时刻比较并替换,在这个时间差里可能导致数据变化

      1. 比如线程 one 和线程 two 都在内存 v 的位置取得 A,线程 two 将值变为 B,然后又变为 A,这时候 one 进行 CAS 发现内存中还是 A,然后 one 操作成功,中间数据的变化没有被感知到。
    2. 原子引用(AtomicReference)

      1. 通过该类包装实体类,实现原子性

        AtomicReference包装实体类

    3. 原子引用 + 修改版本号(类似时间戳)(AtomicStampedReference)

      1. 每次修改版本号变了(+1),其他线程回写时如果版本号不对,不能更新

        AtomicStampedReference解决CAS示例

  4. ArrayList 是线程不安全的,写一个示例并给出解决方案。(Set 和 Map 同理)

    1. 异常信息:java.util.ConcurrentModificationException 并发修改异常 ArrayList线程不安全

    2. 导致原因:并发争抢修改导致,一个线程在写,另一个线程抢夺,导致异常

    3. 解决方案

      1. new Vector<>();使用vector,底层加了synchronized,一般不用

      2. Collections.synchronizedList(new ArrayList<>()) 使用List的父类提供的方法

      3. new CopyOnWriteArrayList<>(); 使用 JUC 包中的类,写时复制:每次copy一个副本,扩容1,写入数据,将原副本的引用指向新副本(替换掉原副本),通知其他线程该资源已修改,释放锁(Reentant Lock)

        ArrayList线程不安全示例和解决方案

    4. HashSet 底层是HashMap,存进去的值是map的key,value 统一都是叫 PRESENT 的对象

  5. 公平锁/非公平锁/可重入锁/递归锁/自旋锁?手写一个自旋锁?

    1. 公平锁/非公平锁 new ReentrantLock(); 参数为 true 创建公平锁,为空或者 false 创建非公平锁。如果后来的线程没抢到锁,就采用公平锁方式(去排队)

      1. 公平锁:按照申请锁的顺序来获取锁,先到先得
      2. 非公平锁:可能存在后申请锁的线程比先申请锁的线程优先获取锁,在高并发的情况下可能造成优先级反转或者饥饿现象(不停有线程优先获取锁,其他线程无限等待)。
      3. 非公平锁吞吐量 > 公平锁
      4. Synchronized 也是非公平锁
    2. 可重入锁(递归锁)

      1. 是什么:有两个嵌套的同步方法,线程获取到外部方法的锁后,可以进入内层函数 线程可以进入任何一个它已经拥有锁所同步着的代码块
      2. 作用:防止死锁
    3. 自旋锁

      1. 尝试获取锁的线程不会阻塞,而是采用循环的方式去尝试获取锁,好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU。

        手写一个自旋锁

    4. 独占锁(写锁)/共享锁(读锁)/互斥锁

      ReentrantReadWriteLockDemo

      1. synchronized 和 ReentrantLock 都是独占锁
      2. ReentrantReadWriteLock 的读锁是共享锁,写锁是独占锁
      3. 读锁的共享锁可保证高效并发读,读写,写读,写写的过程是互斥的。
  6. CountDownLatch / CyclicBarrier / Semaphore 使用过嘛?

    1. CountDownLatch :阻塞调用 await() 的线程,其他线程调用 countDown() 方法为计数器减1,直到计数器为0时,唤醒被 await() 阻塞的线程。

      CountDownLatchDemo

    2. CyclicBarrier :可循环使用的屏障。一组线程到达屏障(同步点)时被阻塞,知道最后一个线程到达,所有被屏障拦截的这组线程才能继续执行。线程通过 await() 方法进入屏障。

      CyclicBarrierDemo

    3. Semaphore :(信号灯)主要用于两个目的:一是用于多个资源的互斥使用,另一个用于并发线程数的控制。

      SemaphoreDemo

  7. 阻塞队列了解嘛?队列为空时,取元素被阻塞。队列满时,加元素被阻塞。

    1. 阻塞队列有没有优点?

      1. 开发者不需要考虑何时阻塞 / 唤醒线程,这些操作交给阻塞队列。
    2. 阻塞队列核心方法

      阻塞队列核心方法示例

    3. 阻塞队列应用

      1. 生产者消费者模式:

        1. 传统版:ProdConsumer_TraditionDemo

          ProdConsumer_TraditionDemo

        2. 阻塞队列版:ProdConsumer_BlockQueueDemo

          ProdConsumer_BlockQueueDemo

      2. 线程池

      3. 消息中间件

    4. Synchronized 和 lock 有什么区别?

      SyncAndReentrantLockDemo

      1. 原始构成不同:synchronized 时 JVM 的关键字,底层通过 monitor 对象来完成,wait / notify 方法只有在 monitor 的同步代码块中才能调用。一个 monitorenter 对应两个 monitorexit,正常退出和异常退出,防止出现底层死锁等问题。lock 是 api 层面的锁,是 new 出来的。
      2. 使用方法:sychronized 不需要用户手动释放,当同步代码执行完成后系统会自动释放对锁的占用。lock 则需要用户手动释放锁,否则可能导致死锁。
      3. 等待是否可中断:sychronized 不可中断,除非抛异常或者运行完成。lock 可中断,调用 interrupt() 中断。
      4. synchronized 是非公平锁。ReentrantLock 默认为非公平锁,可以通过构造方法传入true 为公平锁,false 为非公平锁。
      5. synchronized 不能绑定多个条件 condition。ReentrantLock 可以绑定多个 condition,用于实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized 要么随机唤醒一个,要么全部唤醒。
    5. 不得不阻塞时如何管理?

  8. 谈谈线程池