代码仓库: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. 谈谈线程池

    1. 线程池的优势

      1. 作用:控制运行的线程数量,处理过程中将任务放入队列,在线程创建后启动任务,如果线程数量超过最大数量,超出的线程排队等候,等其他线程执行完再从队列取出任务执行
      2. 特点:线程复用,控制最大并发数,管理线程。
      3. 一:降低资源消耗,通过复用已创建的线程降低线程创建和销毁造成的消耗。
      4. 二:提高响应速度。当任务到达时,不需要等待线程创建即可执行
      5. 三:提高线程的可管理性。调优和监控。
    2. 线程池如何使用

      1. newFixedThreadPool 固定线程数 可能出现巨大的阻塞队列,导致 OOM
      2. newSingleThreadExecutor 单个线程 可能出现巨大的阻塞队列,导致 OOM
      3. newCachedThreadPool 可扩展的线程池 可能创建大量线程,导致 OOM

      线程池使用

    3. 线程池的重要参数 ThreadPoolExecutor

      1. corePoolSize 核心线程数(今日当值线程)
      2. maximumPoolSize 最大线程数 必须大于等于1
      3. keepAliveTime 多余空闲线程存活时间
      4. Unit 时间单位
      5. 阻塞队列
      6. ThreadFactory 创建线程 一般使用默认方法
      7. handler 拒绝策略
    4. 谈谈线程池底层工作原理

    5. 拒绝策略

      1. AbortPolicy 默认 抛异常
      2. CallerRunsPolicy 不抛弃任务,不抛异常,将任务回退到调用者,减少请求数
      3. DiscardOldestPolicy 抛弃等待队列中等待最久的任务,然后把当前任务加入队列再尝试提交当前任务
      4. DiscardPolicy 直接丢弃任务,不处理也不抛异常