CC 咖啡猫的工作空间 Coding Space
  1. 进程与线程
  • 进程是程序的一次执行过程,是系统运行程序的基本单位
  • 线程是程序中最小的执行单元,一个进程可以包含多个线程,同类的多个线程共享进程的堆和方法区资源,但每个线程自己的程序计数器虚拟机栈本地方法栈
  • Java线程与系统线程的区别:现在的 Java 线程的本质其实就是操作系统的线程,JVM 直接使用操作系统原生的内核级线程(内核线程)来实现 Java 线程,由操作系统内核进行线程的调度和管理(一个用户线程对应一个内核线程)。
  • 程序计数器为什么是私有的?程序计数器(用于记录当前线程执行的位置)私有主要是为了线程切换后能恢复到正确的执行位置
  • 虚拟机栈:每个 Java 方法在执行之前会创建一个栈帧用于存储局部变量(代码块和方法中的变量)表、操作数栈、常量池引用等信息
  • 本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务
  • 为了保证线程中的局部变量不被别的线程访问到,虚拟机栈本地方法栈是线程私有的
  • 线程的生命周期:
    • new:就绪状态
    • runnable:运行状态,线程被调用了 start(),还在等待运行的状态(操作系统层面:处于ready状态,获得CPU时间片后就处于运行状态running)
    • blocked:阻塞状态,线程获取锁资源失败并进入休眠状态,需要等待锁释放(争夺锁失败,锁被其他线程持有,退出阻塞状态条件:持有锁的线程释放锁,且本线程抢到)
    • waiting:等待状态,该线程调用无超时等待方法(如Object.wait()、Thread.join()、LockSupport.park()),主动交出执行权,并进入休眠状态(退出休眠状态:需要等待其他线程做出一些特定动作如通知或中断)。
    • time_waiting:超时等待,该线程主动调用带超时等待方法(如Thread.sleep(ms)、Object.wait(ms)、Thread.join(ms)、lock.tryLock(time) 多了超时参数。时间一到自动恢复,不用等别人叫)
    • terminated:终止状态,表示该线程已经运行完毕
  • Object.wait(): 线程必须在synchronized代码块内调用该方法,交出执行权,线程被唤醒后,需要重新获取锁资源(JVM规定,调用wait方法的线程必须是该对象监视器(Monitor)的当前持有者)。
  • Object#wait()和Thread#sleep()的区别
    • 实例wait()是对象实例方法,sleep()是静态方法
    • sleep()在任意地方调用,不释放锁;sleep()线程必须在synchronized代码块内调用,调用时瞬间释放当前对象监视器锁
    • 唤醒方法:sleep()时间到自动醒/interrupt() 中断; wait()需要notify()/notifyAll() 显式唤醒 / 超时自动醒(wait(ms))
    • 线程状态:调用sleep()后线程进入TIME_WAITING状态;wait()调用后进入WAITING或者TIME_WAITING
    • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行
  • 创建线程的方法:继承Thread类、实现Runnable接口、实现Callable接口、使用线程池、使用CompletableFuture类等等,严格来说,Java 就只有一种方式可以创建线程,就是通过new Thread().start()创建
  1. 多线程
  • 线程更轻量级,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程,开销较小
  • 单核CPU支持多线程吗?操作系统通过时间片轮转的方式,将 CPU 的时间分配给不同的线程。Java 使用的线程调度是抢占式的,也就是说,JVM 本身不负责线程的调度,而是将线程的调度委托给操作系统。
  • 并发编程的三个特性:原子性、可见性、有序性
  1. 线程死锁
  • 线程死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放
  • 死锁产生的四个条件:互斥条件、请求与保持条件、不剥夺条件、循环等待条件
  1. Java内存模型
  • CPU缓存:解决 CPU 处理速度和内存处理速度不对等的问题。
  • 指令重排序:系统在执行代码的时候并不一定是按照你写的代码的顺序依次执行
  • Java 源代码会经历 编译器优化重排 —> 指令并行重排 —> 内存系统重排 的过程,最终才变成操作系统可执行的指令序列。
  • JMM:Java 内存模型抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中
  • happens-before原则
  1. volatile
  • 保证变量的可见性(禁用 CPU 缓存,如果我们将一个变量使用 volatile 修饰,这就指示编译器,这个变量是共享且不稳定的,每次使用它都到主存中进行读取)
  • 防止 JVM 的指令重排序:如果volatile使用声明变量,在对这个变量进行读写操作的时候,会通过插入特定的内存屏障的方式来禁止指令重排序
  • volatile 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。
  • JMM 中的 happens-before 原则是判断数据是否存在竞争、线程是否安全的重要依据。volatile 变量的读写操作与 happens-before 原则有着密切的关系(如果线程 A 写入了一个 volatile 变量,线程 B 随后读取了同一个 volatile 变量,那么线程 A 在写入 volatile 变量之前所做的所有修改(包括对非 volatile 变量的修改),对线程 B 都是可见的)。
  1. 乐观锁和悲观锁
  • 乐观锁实现
    • 版本号机制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功
    • CAS算法:Java原子类
  1. synchronized
  • 重量级锁:JVM的监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高
  • 锁优化:在 Java 6 之后, synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销
  • 锁的升级:锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
  • synchronized 与 volatile
    • volatile 关键字是线程同步的轻量级实现,但只能修饰变量,synchronized修饰代码块和方法
    • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证
    • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性
  1. ReentrantLock
  • ReentrantLock 实现了 Lock 接口,是一个可重入且独占式的锁,和 synchronized 关键字类似。不过,ReentrantLock 更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能
  • synchronized 和 ReentrantLock:
    • 都是可重入锁;
    • synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API;
    • ReentrantLock 比 synchronized 增加了一些高级功能:等待可中断、可实现公平锁(synchronized只支持非公平锁)、通知机制更强大、支持超时
  1. StampedLock:读写锁(不可冲入),适合读多写少的场景,可作为ReentrantReadWriteLock的替代品
  2. Atomic原子类
  • 基本类型:AtomicInteger、AtomicLong、AtomicBoolean
  • 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • 引用类型:AtomicReference
  • 对象属性的修改类型:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
  1. ThreadLocal类
  • ThreadLocal是线程局部变量,为每个线程提供独立的变量副本,实现线程间数据隔离,互不干扰
  • 底层原理:每个Thread对象内部维护一个ThreadLocalMap,key为ThreadLocal实例,value为线程局部变量值
  • 内存泄漏风险:ThreadLocalMap使用弱引用key(ThreadLocal),但value是强引用,线程长时间存活会导致value无法回收
  • 最佳实践:使用完ThreadLocal后必须调用remove()方法清理,避免内存泄漏,不要存大对象(推荐轻量级 ID、枚举、状态码)
  • 常见应用场景:
    • 用户会话信息存储userId,日志链路追踪traceId等
    • 数据库连接管理、事务上下文传递(Java 企业级开发中的行业标准实践,几乎所有主流框架Spring、Hibernate、MyBatis、Dubbo 等底层都重度依赖 ThreadLocal)
    • 非线程安全对象的线程级缓存(SimpleDateFormat 或 DecimalFormat 不是线程安全的,多线程共用会报错,比new更省内存)
  • 面试高频考点:
    • ThreadLocal与synchronized的区别:ThreadLocal以空间换时间,synchronized以时间换空间
    • 内存泄漏原因及解决方案:弱引用key + 强引用value的问题,必须手动remove()
    • ThreadLocalMap的hash冲突解决:采用线性探测法而非链表
    • initialValue()方法的作用:提供默认初始值,避免null值判断
    • 父子线程数据传递:普通ThreadLocal无法跨线程传递,需使用InheritableThreadLocal
  • InheritableThreadLocal:子线程可以继承父线程的ThreadLocal值,适用于线程池场景需配合TransmittableThreadLocal使用
  1. 线程池
  • 线程池是一种管理和复用线程资源的机制,通过预先创建一定数量的线程,避免频繁创建销毁线程的开销,同时实现任务的高效调度和执行。
  • 线程池的四大组件
    • 线程控制数:corePoolSize/maximumPoolSize,决定池中“干活的线程”数量规模,必须按业务压测显式设置,禁用默认值
    • 线程任务队列:workQueue,存放已提交但尚未被线程执行的任务,必须用有界队列,严禁无界防 OOM
    • 线程工厂:threadFactory,负责创建新线程,可定制线程属性,必须带业务前缀,否则线上无法排查
    • 拒绝策略:handler,队列满+线程达上限时,对新任务的处理方式,核心业务必须自定义或 CallerRuns,禁默认
  • 创建线程池:通过 ThreadPoolExecutor 构造函数直接创建 (推荐)、通过 Executors 工具类创建 (内置线程池不推荐)
  • 线程池的常见参数:
    • corePoolSize:线程池的核心线程数量
    • maximumPoolSize:线程池的最大线程数,当线程数大于该值,则创建非核心线程执行
    • workQueue:任务队列,用来储存等待执行任务的队列
    • keepAliveTime:当线程数大于核心线程数时,多余的空闲线程存活的最长时间
    • unit:keepAliveTime 参数的时间单位
    • threadFactory:线程工厂,用来创建线程,一般默认即可
    • handler:拒绝策略,达到上线线程数时(线程池数量大于maximumPoolSize),执行拒绝策略
  • 线程池的拒绝策略:
    • AbortPolicy: 默认拒绝策略,直接抛出 RejectedExecutionException,非核心业务,适合允许快速失败
    • CallerRunsPolicy:调用者线程自己执行该任务,适用于核心业务(自动降级同步,防丢数据+自带背压)
    • DiscardPolicy:silently 丢弃新任务,适合监控埋点、日志等可丢失场景
    • DiscardOldestPolicy:丢弃队列最老的未处理任务,然后重新尝试执行任务,适合实时性要求高、允许丢弃旧数据
  • 线程池处理任务的流程(当向线程池提交一个任务时)
    1. 检查核心线程是否已满,如果当前运行的线程数 < corePoolSize,创建核心线程
    2. 尝试加入工作队列,如果当前线程数 >= corePoolSize且工作队列未满,将任务放入工作队列等待执行(此时不会创建新线程,而是等待空闲的核心线程从队列中取出任务)
    3. 创建非核心线程,工作队列已满且当前线程数 < maximumPoolSize,:创建新的非核心线程来立即执行任务(如果这些线程在空闲超过keepAliveTime后会被回收)
    4. 工作队列已满且线程数已达到maximumPoolSize,根据配置的RejectedExecutionHandler执行拒绝策略
  • 常用应用:
    • 本地异步执行:主流程不阻塞,后台静默处理(@Async,配置线程池)
    • 并发加速:拆分大任务,多线程并行计算后聚合结果
    • 资源隔离:慢任务/高危任务独立线程池,避免拖垮主线程(Tomcat 处理 HTTP 请求,但“导出报表”任务扔给独立线程池,防止占满 Web 线程)
    • 流量削峰:通过队列容量+拒绝策略控制瞬时并发。如秒杀场景:请求进内存队列,线程池按固定速率消费,超出的直接拒绝或降级
    • 定时/延迟任务:ScheduledThreadPoolExecutor 替代 Timer?
  1. Future模式
  • Future类的异步思想运用,主要用在一些需要执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成
  • CompletableFuture 类:Future 在实际使用过程中存在一些局限性,比如不支持异步任务的编排组合、获取计算结果的 get() 方法为阻塞调用
  • Future核心方法:
    • get():阻塞获取结果,可设置超时时间防止无限等待
    • isDone():判断任务是否完成
    • cancel():尝试取消任务执行
    • isCancelled():判断任务是否被取消
  • CompletableFuture优势:
    • 链式调用:thenApply/thenAccept/thenRun等方法支持函数式编程
    • 组合操作:allOf/anyOf支持多个异步任务的组合
    • 异常处理:exceptionally/whenComplete提供完善的异常处理机制
    • 非阻塞:所有操作都是异步非阻塞的
  • 面试高频考点:
    • Future.get()阻塞原理:基于AQS的Condition.await()
    • CompletableFuture线程池选择:默认使用ForkJoinPool.commonPool(),可自定义线程池
    • 异步任务编排:如何优雅处理多个异步任务的依赖关系
    • 超时控制:CompletableFuture没有内置超时,需结合delayedExecutor实现
  1. AQS
  • 抽象队列同步器,位于java.util.concurrent.locks 包
  • CLH队列:CLH队列(Craig, Landin, and Hagersten)
  • AQS核心设计:
    • state变量:int类型的同步状态,通过CAS操作保证原子性
    • FIFO等待队列:双向链表结构,存储等待获取锁的线程节点
    • 模板方法模式:子类通过重写tryAcquire/tryRelease等方法定义同步逻辑
  • 独占模式 vs 共享模式:
    • 独占模式:同一时刻只有一个线程能获取资源(如ReentrantLock)
    • 共享模式:同一时刻多个线程可同时获取资源(如Semaphore/CountDownLatch)
  • 常见实现类:
    • ReentrantLock:独占模式,可重入
    • ReentrantReadWriteLock:读写锁,读共享写独占
    • Semaphore:信号量,控制并发数量
    • CountDownLatch:倒计时门闩,等待多个线程完成
    • CyclicBarrier:循环屏障,线程相互等待
  • 面试高频考点:
    • AQS如何避免死锁:通过CLH队列的FIFO特性保证公平性
    • state变量的作用:表示锁的状态或资源数量
    • Condition的实现原理:每个Condition对应一个等待队列
    • 自旋优化:在阻塞前先自旋尝试获取锁,减少线程切换开销
  1. 虚拟线程:jdk21正式引入,略
  • 虚拟线程(Virtual Threads)是JDK21的正式特性,属于Project Loom项目
  • 核心特点:
    • 轻量级:由JVM管理而非操作系统,创建成本极低(KB级别内存)
    • 大量并发:可轻松创建百万级虚拟线程
    • 与平台线程映射:M:N映射关系,多个虚拟线程映射到少量平台线程
    • 无缝兼容:现有代码无需修改即可使用虚拟线程
  • 使用方式:
    • Thread.startVirtualThread(Runnable):直接启动虚拟线程
    • Executors.newVirtualThreadPerTaskExecutor():虚拟线程执行器
    • Structured Concurrency:结构化并发API(JDK21预览)
  • 适用场景:
    • IO密集型应用:Web服务器、数据库连接、文件操作等
    • 高并发服务:微服务、API网关等需要处理大量并发请求的场景
    • 不适用CPU密集型:虚拟线程无法提升CPU计算性能
  • 面试高频考点:
    • 虚拟线程vs平台线程:轻量级vs重量级,JVM管理vs操作系统管理
    • 内存占用对比:虚拟线程KB级别,平台线程MB级别
    • 与协程区别:Java虚拟线程是真正的线程语义,协程是协作式调度
    • 性能提升原理:减少线程上下文切换开销,提高IO并发能力