并发编程 Java 面试题 真正的 offer 偏方 Java 基础 Java 高级

并发编程 Java 面试题 真正的 offer 偏方 Java 基础 Java 高级

一、参考资料

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

二、笔记总结

01、引言

  • 从容应对面试官提的问题
  • 提升自己的技术水平和技术视野

02、谈一谈你对 AQS 的理解

  • AQS 是 AbstractQueuedSynchronizer 的简称,是并发编程中比较核心的组件。
  • AQS 是线程同步器,它是 JUC 包中多个组件的底层实现。
  • AQS 提供了两种锁机制,排它锁和共享锁。
    • 排它锁,在多个线程竞争同一个共享资源时,同一时刻只允许一个线程访问该共享资源。
    • 共享锁,也成为读锁,就是在同一时刻,允许多个线程同时获得锁资源。

03、Lock 和 Synchronized 的区别

  • 从功能角度看 Lock 和 Synchronized 都是 Java 中解决线程安全问题的工具。
  • 从特性来看,Synchronized 是 Java 中的同步关键字,Lock 是 JUC 包中提供的接口。
  • Synchronized 中可以通过两个方式来控制锁的力度
    • 第一种是加到方法上,第二种是加到代码块上
    • 并且可以通过 Synchronized 加锁对象的声明周期来控制锁的作用范围
    • 比如锁对象是静态对象或类对象时,这个锁就是全局锁。
    • 如果锁对象是一个普通实例,那么这个锁的范围取决于这个实例的声明周期。
  • Lock 锁的力度是通过它里面提供的 lock 和 unlock 方法决定的,包裹在这两个方法之间的代码能够保证线程安全性,锁的作用域取决于 Lock 实例的声明周期。
  • Lock 比 Synchronized 灵活性更高,Lock 可以自主决定什么时候加锁什么时候释放锁,只需要调用 lock() 和 unLock() 这两个方法就行了。
  • Lock 还提供了非阻塞的竞争锁方法 tryLock(),这个方法通过返回 true/false,来告诉其它线程是否已经有其它线程正在使用锁。
  • Sychronized 锁的释放是被动的,同步代码块执行完以后或出现异常时才会释放。
  • Lock 提供了公平锁和非公平锁机制
    • 公平锁,线程竞争锁资源时,如果已经有其它线程正在排队等待锁资源释放,那么当前线程无法插队。
    • 非公平锁,不管是否有线程在排队等待锁,当前线程都会尝试竞争一次锁。
  • 从性能方面来看 Synchronized 和 Lock 的差别不大

04、线程池如何知道一个线程的任务已经执行完成

  • 在线程池内部,当我们把一个线程丢给线程池去执行的时候,线程池会调度工作线程来去执行这个任务的 run() 方法,run() 方法如果正常结束,也就意味着这个任务完成了,所以线程池中的工作线程是通过同步调用任务的 run() 方法并且等待 run() 方法执行结束后,再去统计任务的完成数量的。
  • 在线程池外部,获取线程池内部的任务执行状态
    • 线程池提供了 isTerminated() 方法来判断线程池的运行状态,我们可以循环循环判断 isTerminated() 方法的返回结果来判断线程池的运行状态,一旦线程池的运行状态是 Terminated 那么线程池中的所有任务就已经运行完成了。使用 isTerminated() 方法的前提是程序中主动调用了线程池的 shutdown() 方法。
    • 在线程池中有一个 submit() 方法,它提供了一个 Future 的返回值,我们可以通过 Future.get() 方法来获取任务的执行结果
    • 使用 CountDownLatch 计数器,如下图:
    • 在线程池里还有一个回调方法,我们可以在回调方法中进行一个判断,也是同样一个逻辑。

05、程序员如何描述项目经历

  • 我做了一个什么项目,这个项目是解决什么问题的,项目里面包含哪些功能,我在里面负责什么,以及项目用了什么技术栈,一分钟左右结束。

06、认知提升篇,如何 Java,如何才能年薪 60 w

  • 对于 30 w 到 60 w 薪资范围来说,技术上必须形成完整的体系,才能够灵活利用这些技术去提升架构设计能力。
    • Java 基础
    • 应用架构
    • 千万级流量架构的设计方法
    • 性能调优
    • 应用监控
    • 项目实战
    • 云原生容器化
    • 技术框架源码

07、什么叫阻塞队列的有界和无界

08、ConcurrentHashMap 底层具体实现原理

09、谈一下 CAS 机制

10、死锁发送的原因和怎么避免

  • 死锁简单来说就是两个或两个以上线程在执行过程中去争夺同一个共享资源,造成的相互等待的一个现象。
  • 如果没有外部干预呢线程会一直阻塞,无法往下去执行,这样一直处于相互等待资源的线程我们称为死锁线程。
  • 导致死锁的条件有四个,同时满足这四个条件就会产生死锁:
    • 第一,互斥条件,共享资源 X 和 Y 只能被一个线程占用。
    • 第二,请求和保持条件,线程 T1 在取得共享资源 X 时去请求共享资源 Y 不释放共享资源 X。
    • 第三,不可抢占条件,其它线程不能强行抢占线程 T1 占用的资源。
    • 第四,循环等待条件,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占用的资源。
  • 导致死锁之后,只能通过人工干预来解决,重启服务或 kill 线程,所以我们只能在写代码时去规避可能出现的死锁问题,我们只需要破坏发生死锁的四个条件中任意一个就可以解决死锁问题,比如:
    • 对于请求和保持这个条件,我们可以一次性获取所有资源,这样就不存在锁要等待了。
    • 对于不可抢占这个条件,占用部分资源的线程在进一步申请其它资源的时候,如果申请不到我们可以主动释放它占用的资源。
    • 对于循环等待这个条件,可以按序申请资源来预防,按序申请指资源是有线性顺序的,先申请资源序号小的再申请资源序号大的,这样线性化之后,就不存在循环了。

11、wait 和 notify 为什么要在 synchronized 代码块中

  • wait 和 notify 用来协调多个线程
    • wait 表示让线程进入阻塞状态
    • notify 表示让阻塞的线程被唤醒
  • 如果一个线程被 wait 方法阻塞那必要需要另一个线程调用 notify 方法唤醒,从而实现多个线程的通信。
  • 在多线程中可以使用共享变量实现多个线程间的通信
    • 线程 t1 修改共享变量 s,线程 t2 在线程 t1 修改完成后读取共享变量 s,但是多线程本身具有并行执行的特征,也就是说在同一个时刻多个线程可以同时来执行,那么这种情况下,线程 t2 在读取共享变量 s 之前,必须要知道线程 t1 已经修改完成了,否则就需要等待。同时修完完成后,要把处于等待状态的线程唤醒。
    • 所以在这种情况下,必须有一个静态条件,用于判断控制线程什么时候等待,什么时候唤醒。synchronized 就可以实现这样一个互斥的条件,参与通信的线程必须要竞争到共享变量的锁资源,才能有资格修改变量,修改完成后释放锁,其它线程就可以竞争到锁获取修改之后的共享数据,从而完成多个线程通过共享数据通信。这就是为什么wait 和 notify 为什么要在 synchronized 代码块中的原因。
  • 有了 synchronized 同步锁就可以实现对于多个通信线程间的一个互斥,从而去实现条件等待和条件唤醒。另外为了避免 wait 和 notify 的错误使用,JDK 强制要求把 wait 和 notify 写在了同步代码块中,否则会抛出异常。

12、怎么理解线程安全

  • 线程安全有三个方面,原子性、有序性、可见性。
  • 原子性是说,一个线程执行一系列程序指令时应该是不可中断的。CPU 的任务调度是导致原子性问题的核心,JVM 中提供了 Synchronized 关键字来去解决这个问题。
  • 可见性是说,在多线程环境中,读和写是发生在不同线程中的,有可能会出现某个线程对共享变量的修改对其它线程不是实时可见的。
  • 有序性是说,程序编写的指令顺序和最终程序运行的指令顺序可能会出现不一致的现象,这种现象可以称为指令重排序。
  • 可见性和有序性可通过 JVM 提供的 Volatile 关键字解决。

13、什么是守护线程

  • 守护线程是专门为用户线程提供服务的线程,它的声明周期是依赖用户线程的。
  • 只有 JVM 仍然存在用户线程的情况下,守护线程才有存在的意义,否则一旦 JVM 进程结束了,那么守护线程也会随之结束。
  • 也就是说守护线程不会阻止 JVM 的退出,用户线程会。
  • 守护线程和用户线程的创建方法是一样,只需要调用用户线程的 setDaemon 方法去设置成 true 就好了。
  • 守护线程适合后台的通用服务型的场景,比如 JVM 垃圾回收。

14、AQS 为什么采用双向链表

  • 双向链表提供了双向指针,可以在任何一个节点向前或向后进行遍历,这种对于有反向遍历的场景来说非常有用。
  • 双向链表可以在任意位置实现数据的插入和删除,并且操作的时间复杂度是 O(1) 常量级,不受链表长度的影响,这种对于需要频繁对链表进行增删操作的场景非常有用。
  • 采用单向链表不支持双向遍历,而 AQS 中存在需要双向遍历的场景来提升线程阻塞和唤醒的效率。

15、线程池是如何实现线程复用的

  • 线程池里面采用了一个生产者消费者的模式去实现线程的复用。
  • 生产者消费者模型其实就是通过一个中间容器来去解耦生产者和消费者的任务处理过程,生产者不断生产任务保持到容器中,消费者不断从容器中消费任务。
  • 在线程池中需要保证工作线程的重复使用,这些线程有任务的时候执行,没有任务的时候等待并释放 CPU 的资源,所以它使用阻塞队列实现这样一个需求。
    • 提交任务到线程池里面的线程,称为生产者线程,这些任务会保存到线程池的阻塞队列中,然后线程池中的工作线程不断的从阻塞队列中获取任务去执行。
    • 基于阻塞队列的特性使得这些工作线程如果没有任务时就会等待,直到有新的任务进来时这些工作线程被唤醒,从而达到线程复用的目的。

16、Volatile 关键字有什么用

  • 保证多线程环境下对共享变量的可见性
  • 通过增加内存屏障防止多个指令重排序
  • 可见性,一个线程对共享变量的修改,其它线程可以立即看到。
  • 指令重排序,指令编写的顺序和执行的顺序不一致,从而在多线程环境下导致可见性问题。

17、ThreadLocal 是什么

  • ThreadLocal 是一种线程隔离机制,它提供了多线程环境下对于共享变量访问的安全性。
  • 在多线程访问共享变量这个场景中,一般解决办法是对共享变量加锁,从而保证这同一个时刻只有一个变量能对共享变量进行更新。
  • 但是加锁会导致性能下降,所以 ThreadLocal 用了一种空间换时间的设计思想,也就是说在每个线程中都有一个容器来存储共享变量的一个副本,然后每个线程只对自己的变量副本做更新操作,这样解决线程安全问题,有避免了多线程竞争锁的开销。

18、基于数组的阻塞队列 ArrayBlockingQueue 原理

  • 阻塞队列是在队列的基础上增加了两个附加操作
    • 在队列为空时,获取元素的线程会阻塞等待队列变为非空。
    • 当队列满了时,存储元素的线程会阻塞等待队列变为可用。
  • 阻塞队列可以实现生产者消费者模型。
  • 实现阻塞队列需要用到两个技术
    • 队列元素的存储
    • 线程的阻塞和唤醒
  • ArrayBlockingQueue 是基于数组结构的阻塞队列,队列元素存储在数组结构里面,由于数组的长度有限所以使用了循环数组。

19、伪共享的概念以及如何避免

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=19\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

20、什么是可重入锁

  • 一个线程如果抢占到了互斥锁的资源,在锁释放之前再去抢占同一把锁时不需要等待,只需要记录重入次数。
  • 在多线程并发编程中绝大多数锁都是可重入的:
    • Synchronized
    • ReentrantLock
  • 也有一些锁不支持可重入:
    • 比如 JDK 8 提供的读写锁 StampedLock
  • 锁的可重复性主要解决死锁问题,因为一个已经获得同步锁的线程在释放锁之前再去竞争锁的时候,相当于会出现一个自己等待自己释放锁的情况,这很显然会导致死锁。

21、ReentrantLock 的实现原理

  • ReentrantLock 是可重入排它锁,主要解决多线程对于共享资源竞争的问题。

22、简述一下你对线程池的理解

  • 线程池的本质是一种池化技术,池化技术是一种资源复用的设计思想。
  • 线程池的设计目的:
    • 1、减少线程的频繁创建和销毁带来的性能开销。
    • 2、线程池本身有参数控制创建的线程数量,避免大量创建线程导致的资源利用率过高,起到了资源保护的作用。
  • 线程池中的工作线程处于一直运行的状态,从阻塞队列中获取待执行的任务,一旦队列空了,工作线程就会被阻塞,直到有新的任务进来。
  • 线程池的关键参数,核心线程数量和最大线程数量,核心线程数表示默认长期存在的工作线程,最大线程数是根据任务的情况来动态创建的线程。

23、如何中断一个正在运行的线程

  • Java Thread 中提供了一个 stop 方法可以去强制终止,但是这种方式不安全,因为有可能线程的任务还没有执行完成,导致运行结果不正确的现象。
  • 要想安全的中断一个线程,需要在线程中埋下一个钩子,外部线程通过钩子触发线程的中断命令。
    • Java thread 里面提供了一个 interrupt 方法,配合 isInterrupt 方法使用实现线程的安全中断,告诉正在运行的线程可以停止,是否停止取决于正在运行的线程,保证线程运行结果的安全性。

24、Synchronized 锁升级的原理

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=24\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

25、什么是守护线程

  • 守护线程是专门为用户线程提供服务的线程,生命周期依赖用户线程,只有 JVM 依然存在用户线程的情况下,守护线程才有存在的意义,一旦 JVM 进程结束了,守护线程也会随之结束。
  • 守护线程不会阻止用户线程的退出,用户线程会。
  • 守护线程和用户线程的创建方式是一样的,只需要调用用户线程的 setDaemon 方法设置成 true 就行。
  • 守护线程更适合后台的通用型场景
    • JVM 的垃圾回收就是一个典型的使用场景,这个场景的特殊点在于当 JVM 进程结束的时候,内存的回收线程本身就没有存在的意义了,所以不能因为有内存正在回收导致 JVM 进程无法结束。
    • 基于守护线程的特性,不能应用在一些线程池或 IO 的任务场景里,因为 JVM 退出以后守护线程也会直接退出,就会导致任务没有执行完或资源没有释放的问题。

26、ReentrantLock 是如何实现锁公平和非公平性的

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=26\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

27、什么是 CompletableFuture

  • CompletableFuture 可以让我们将一个耗时的任务提交给线程池进行异步处理,然后继续执行其它任务,等到异步任务执行结束以后,会触发一个回调方法,可以在回调方法中处理异步任务的执行结果。

28、线程状态 BLOCKED 和 WATTING 有什么区别

  • BLOCKED 和 WATTING 都属于线程的阻塞等待状态
  • BLOCKED 状态是指线程在等待监视器锁时的状态,也就是说多个线程竞争 Synchronized 同步锁的时候没有竞争到锁的线程会被阻塞等待。
  • WATTING 状态表示线程的等待状态,可以使用 wait join park 等方法使线程进入 WATTING 状态,使用 notify unpark 等方法唤醒阻塞的线程。
  • BLOCKED 是锁竞争失败后被动触发的状态,WATTING 是主动触发的状态。BLOCKED 的唤醒是自动触发的,WATTING 状态必须通过特定方法主动唤醒。

29、Thread 和 Runnable 的区别

  • Thread 是一个类,Runnable 是一个接口,类是单一继承,接口是多继承,已经存在继承关系的类需要实现线程只能实现 Runnable 接口。
  • Thread 类实现了 Runnable 接口,使用 Thread 类或 Runnable 接口都需要实现 run 方法。

30、AQS 为什么要使用双向链表

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=30\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

31、ConcurrentHashMap 的 size 方法是线程安全的吗

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=31\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

32、wait 和 sleep 是否会触发锁的释放以及 CPU 资源的释放

  • wait 方法会释放锁资源以及 CPU 资源,sleep 方法不会释放锁资源但是会释放 CPU 资源。
  • wait 方法是让线程进入阻塞状态,必须写在 Synchronized 同步代码块中,线程调用 wait 方法表示任务已经处理完成,wait 方法必须释放锁其它线程才能抢到锁,否则就会死锁。
  • sleep 方法只是让一个线程单纯进入睡眠状态。

33、DCL 单例模式设计为什么需要 volatile 修饰实例对象

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=33\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

34、讲下线程池的线程回收

  • 线程池中分为核心线程和非核心线程,核心线程是常驻在线程池的工作线程。
  • 当线程池的任务队列满了的情况在,为了增加线程池的任务处理能力,线程池会增加非核心线程。
  • 由于非核心线程是为了解决在任务过多的情况下临时增加的,所以当任务处理完以后,工作线程处于空闲状态时就需要进行回收。

35、如果一个线程两次调用 start 方法,会出现什么问题

  • 在 Java 里面一个线程只能调用一次 start 方法,第二次调用会抛出异常。
  • 一个线程本身是具备一个生命周期的,在 Java 中线程的生命周期包括六种态。
    • NEW RUNNABLE BLOCKED WATTING TIMED_WAIT TERMINATED
  • 第一次调用 start 方法时,线程处于非 new 的状态,再次调用 start 方法相当于让正在运行的线程再运行一遍,不管是从线程的安全性角度来看还是说从线程本身的执行逻辑来看,它都是不合理的,因为在线程运行时,会先去判断线程的状态。

36、JAVA 官方提供了哪几种线程池,分别有什么特定

  • JDK 默认提供了五种不同的线程池创建方式
  • newCachedThreadPool
    • 最大线程数量是 Integer.MaxValue
    • 线程的存活时间是 60 秒
  • newFixedThreadPool
    • 核心线程数和最大线程数是固定的指,任务处理不过来时会放到阻塞队列里面等待。
  • newSingleThreadExecutor
    • 只有一个工作线程,并且线程数量无法修改。
  • newScheduledThreadPool
    • 具有延迟执行功能的线程池
  • newWorkStealingPool
    • Java 8 新加入的线程池,利用窃取算法并行处理请求。

37、说一下对 Happens-Before 的理解

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=37\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

38、可以说下阻塞队列被异步消费怎么保持顺序吗

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=38\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

39、当任务数超过线程的核心线程数时,如何让他不它入队列

  • 提交一个任务到线程池时工作原理分为四个步骤
    • 预热核心线程
    • 把任务添加到阻塞队列
    • 添加阻塞队列失败,创建非核心线程增加处理效率。
    • 非核心线程达到了阈值,触发拒绝策略。
  • 如果希望任务不进入阻塞队列,只需要影响第二个步骤就好了。
    • 在 Java 的线程池中可以修改阻塞队列的类型。
    • SynchronousQueue 是不能存储任何元素的阻塞队列,没生产一个任务就必须指定一个消费者否则就会阻塞生产者。
    • 修改阻塞队列类型为 SynchronousQueue 就可以避免任务进入阻塞队列,而是启用最大线程数去处理任务。

40、SimpleDateFormat 是线程安全的吗

  • SimpleDateFormat 不是线程安全的,因为 SimpleDateFormat 内部有一个 Calendar 对象引用,用于存储日期信息。
  • 多个线程操作 SimpleDateFormat 会共享 Calendar 对象,会出现数据脏读,导致错误。
  • Java 8 中引入了线程安全的 API,比如 LocalDateTimer DateTimeFormatter 等等。

41、并行和并发有什么区别

  • 并行和并发是 Java 并发编程的概念
  • 并行是指在多核 CPU 架构里面,同一个时刻同时执行多个线程的能力。在单核 CPU 架构中同一个时刻只能运行一个线程,在 4 核 CPU 架构中同一个时刻可以运行四个线程。
  • 并发是指同一个时刻 CPU 能够处理的任务数量,操作系统是可以通过 CPU 时间片机制来提升 CPU 的并发能力的。

42、如何解决死锁问题

  • 在多线程环境里,有两个或两个以上的线程,同时满足互斥条件、请求保持条件、不可抢占条件、循环等待条件。
  • 出现死锁后可以使用 jstack 日志导出线程的 dump 日志,定位到死锁代码,修改代码破坏四个条件的任意一个,就可以解决死锁问题。
  • 互斥条件是锁本身的特性,不能被破坏。

43、为什么 ConcurrentHashMap 中 key 不运行为 null

【25年新版Java面试场景题+八股文!真正的offer偏方,跳槽面试你一定用得上!Java面试丨Java场景题丨Java丨Java基础丨Java高级】 https://www.bilibili.com/video/BV1w44y1w7Zh/?p=43\&share_source=copy_web\&vd_source=855891859b2dc554eace9de3f28b4528

44、ThreadLocal 会出现内存泄露吗

  • 不恰当的使用会导致内存泄露,使用完以后主动使用 remove 方法移除 ThreadLocal 中的数据是最好的方法。

45、说一下你对 CompletableFuture 的理解

相关推荐
兰令水1 小时前
topcode【随机算法题】【2026.5.15打卡-java版本】
java·算法·leetcode
JAVA学习通3 小时前
北京明光云振铎数据科技Java面经
java·开发语言·科技
贫民窟的勇敢爷们9 小时前
SpringBoot整合AOP切面编程实战,实现日志统一记录+接口权限校验
java·spring boot·spring
jerryinwuhan9 小时前
基于各城市站点流量的复合功能比较
开发语言·php
AC赳赳老秦10 小时前
供应链专员提效:OpenClaw自动跟踪物流信息、更新库存数据,异常自动提醒
java·大数据·服务器·数据库·人工智能·自动化·openclaw
迈巴赫车主10 小时前
Java基础:list、set、map一遍过
java·开发语言
灵犀学长10 小时前
基于 Spring ThreadPoolTaskScheduler + CronTrigger 实现的动态定时任务调度系统
java·数据库·spring
南 阳11 小时前
Python从入门到精通day66
开发语言·python
好家伙VCC12 小时前
【无标题】
java