知识点标签

Java 并发面试题解析第 2 页

Java 并发相关面试题解析,按真实面经题目沉淀核心机制、易错点和面试官追问。

126 道题 14 个岗位 18 个公司

Java 并发相关面试题第 2 页

ConcurrentHashMap为什么性能比较好?

ConcurrentHashMap 性能好,本质上不是因为完全没有锁,而是把锁的范围、锁的频率和锁竞争都压到了更低水平。它通过读操作无锁、写操作尽量 CAS、冲突时只锁单个桶、扩容时多线程协作迁移、计数时分散热点等方式,避免了 Hashtable 或直接给 HashMap 加 synchronized 那种全表串行化的瓶颈。回答时要区分 JDK7 的 Segment 分段锁模型和 JDK8 的 Node 数组加 CAS、synchronized 桶级锁模型。

HashMap 的底层实现原理是什么?

HashMap 的核心是用哈希表存储键值对,通过 key 的 hash 值定位数组桶,再在桶内处理哈希冲突。JDK 1.7 主要是数组加链表,链表采用头插法,扩容迁移时在并发场景下可能形成链表环;JDK 1.8 改为数组、链表、红黑树组合,链表采用尾插法,并在冲突严重时树化,把极端情况下的查找从线性复杂度优化到对数复杂度。

了解哪些不同 JDK 版本的新特性?

这道题考察的不是背版本号,而是能否把 JDK 演进和工程实践联系起来。回答时应先按主线概括:JDK 8 是现代 Java 的分水岭,引入 Lambda、Stream、Optional、默认方法和新的日期时间 API;JDK 9 到 10 开始模块化、集合工厂方法、JShell、接口私有方法和局部变量类型推断;JDK 11 作为常用长期支持版本,补齐 HTTP Client、字符串和文件 API、ZGC 初步可用等能力;JDK 17 是当前大量生产系统升级的关键长期支持版本,带来 sealed class、record、switch 表达式、文本块、模式匹配等语言能力,并强化 JVM、GC 和封装边界;JDK 21 进一步把虚拟线程、结构化并发、模式匹配 switch、record pattern、分代 ZGC 等特性推向成熟,重点提升高并发服务的线程模型和表达能力。

Java 线程池的核心参数和执行流程是什么?

这道题考察的不是背出几个构造参数,而是要说明 ThreadPoolExecutor 如何用线程数、队列、线程工厂和拒绝策略共同定义资源边界。高质量回答应先点明线程池复用线程、控制并发、削峰和保护系统的目的,再按任务提交后的执行路径解释:先看核心线程,再入队,再扩容到最大线程,最后触发拒绝策略,同时补充队列选择、参数取舍、异常处理、关闭流程和线上监控。

java中的锁有哪些?

Java 中的锁不能只回答 synchronized 和 ReentrantLock,而要按实现机制、语义特征、等待方式、读写模型和 JVM 优化层级来拆解。面试时应从对象监视器、AQS 显式锁、读写锁、StampedLock、CAS 原子类、LockSupport 与 Condition 这些核心工具出发,再说明乐观/悲观、公平/非公平、可重入/不可重入、自旋/阻塞等分类维度,并结合适用场景、性能取舍和常见陷阱给出判断依据。

设计一个线程安全的HashMap?

设计线程安全的 HashMap,核心不是简单给每个方法加 synchronized,而是先明确一致性、吞吐量、内存、迭代语义等需求边界,再选择合适的锁粒度和扩容协调方案。一个可落地的设计通常会从桶级锁或分段锁出发,保证单个 key 的 put、get、remove 线性化,同时通过安全发布、volatile、CAS、锁顺序和 resize 协议避免数据丢失、死锁、读到半迁移结构等问题;如果追求工业级性能和复杂场景,通常应优先使用 ConcurrentHashMap,而不是自行实现。

ConcurrentHashMap 为什么能保证线程安全?

ConcurrentHashMap 的线程安全不是靠一个全局大锁实现的,而是把可见性、原子性和局部互斥组合起来:数组槽位和节点关键字段用 volatile 保证可见性,空桶插入、初始化和扩容状态切换用 CAS 保证原子更新,非空桶写入时只锁住单个桶的头节点,读操作基本无锁;再配合扩容协作、红黑树化和分散计数,既保证并发访问下结构不被破坏,又尽量降低锁竞争。

java中有哪些以队列、链表为底层实现的数据结构?

Java 里要区分“队列接口”和“链表底层”。Queue、Deque 是行为抽象,表示先进先出、双端队列或优先级访问;LinkedList、ConcurrentLinkedQueue、LinkedBlockingQueue 这类才是链式节点结构。ArrayDeque、ArrayBlockingQueue、PriorityQueue 虽然暴露队列接口,但底层分别是循环数组、有界数组和二叉堆数组,不是链表。LinkedHashMap 则是哈希表加双向链表,用链表维护插入顺序或访问顺序,但它本身不实现 Queue。

怎么保证map的线程安全?

保证 Map 线程安全的核心是先明确并发语义:是只要单次 get/put 安全,还是复合操作也要原子;是允许弱一致迭代,还是必须看到稳定快照;是读多写少、写多读少,还是配置类读多且整体替换。常见方案包括外部加锁、Collections.synchronizedMap、ConcurrentHashMap、不可变 Map、读写锁和快照/COW。面试中不能只说“用 ConcurrentHashMap”,还要说明它解决了什么、不解决什么,以及复合操作、迭代一致性、内存可见性和选型边界。

什么是线程安全?

线程安全指一段代码、对象或组件在多个线程同时访问时,仍然能够保持预期的正确性,不因为执行时序交错而产生脏数据、丢失更新、状态破坏或偶发异常。判断线程安全不能只看单次调用是否正确,而要看共享可变状态在并发读写下是否满足原子性、可见性和有序性,并通过不可变、线程封闭、同步、原子类或并发容器等手段建立可靠的并发语义。

如何在实际中判断是否会出现线程安全问题?

判断实际项目中是否会出现线程安全问题,核心不是先看有没有多线程,而是追踪共享可变状态是否被多个执行路径并发访问,以及访问是否包含读改写、检查后执行、跨字段一致性、对象发布等风险。实战判断要结合代码审查、并发入口梳理、锁边界分析、压测和线上偶发症状,而不是依赖一次本地复现。

单例模式如何实现,如何保证线程安全?

单例模式的核心目标是让一个类在进程内只暴露一个可访问实例,并控制实例创建时机。回答时应从构造器私有化、全局访问点、线程安全发布、延迟加载、反射和序列化破坏边界几个维度展开。常见实现包括饿汉式、同步懒汉式、双重检查锁、静态内部类和枚举单例,其中推荐优先说明静态内部类和枚举单例,再解释为什么双重检查锁必须配合 volatile。

如果要用线程安全的数据结构,有什么替代方案?

使用线程安全数据结构时,核心不是简单把 ArrayList、HashMap 换成带锁版本,而是先判断共享状态是否真的必须共享,再按读写比例、是否需要阻塞、是否需要顺序、是否有复合操作一致性来选型。Java 中常见替代方案包括 ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue、ConcurrentLinkedQueue、同步包装器、不可变快照、ThreadLocal、分段锁、Actor 或消息队列串行化,以及在跨进程场景下使用数据库或缓存的原子能力。

内核多线程模块的线程调度是怎么实现的?

Linux 内核多线程模块的线程调度通常不是模块自己实现调度器,而是模块创建多个内核线程后交给 Linux 统一调度。每个内核线程都有 task_struct,进入某个调度类,挂到 CPU 运行队列上,由 CFS、实时调度类或其他调度类根据状态、优先级、vruntime、CPU 负载和亲和性决定何时运行、抢占、阻塞、唤醒和上下文切换。模块开发者更关注线程创建、等待唤醒、停止退出、锁边界和不可睡眠上下文。

如何创建子进程?

创建子进程的核心方式是调用 fork():内核复制当前进程的进程描述信息,创建一个几乎相同但 PID 不同的新进程;父进程得到子进程 PID,子进程得到返回值 0。典型工程模式是 fork 后,子进程按需执行 exec 系列函数加载新程序,父进程使用 wait 或 waitpid 回收子进程退出状态,避免僵尸进程。Linux 中 fork 并不会立即完整拷贝父进程全部内存,而是依赖写时复制降低成本。面试回答应同时说明资源继承、文件描述符共享语义、信号处理、孤儿和僵尸进程、vfork 与 posix_spawn 的使用边界,以及多线程服务中 fork 后的工程风险。

fork发生复制的时候子进程会复制什么?

fork 的本质不是把父进程的一切都物理复制一份,而是创建一个几乎相同的子进程执行上下文:内核为子进程建立新的进程描述符、PID、虚拟地址空间视图和资源引用关系;用户态内存通常通过写时复制延迟分裂;文件描述符表被复制但底层打开文件对象常被共享。因此面试回答要区分“逻辑复制”“引用共享”和“写时复制后才真正复制”。

进程与线程区别?

进程是操作系统进行资源分配、隔离和保护的基本单位,线程是进程内部的执行流,通常是 CPU 调度的基本单位。进程之间默认拥有独立虚拟地址空间和资源边界,隔离更强;同一进程内多个线程共享地址空间、堆、代码段、打开文件等资源,但各自拥有独立寄存器上下文、栈、线程局部存储和调度状态。

如何定位慢sql,怎么优化?

定位慢 SQL 的核心不是只看执行时间,而是建立从发现、归因、验证到优化的闭环:先通过监控、慢查询日志、数据库性能视图确认慢 SQL;再结合执行计划、索引、扫描行数、锁等待、事务、数据量和业务访问模式判断瓶颈;最后用索引、SQL 改写、分页优化、表结构调整、缓存或架构拆分降低扫描量、排序量、回表量和锁竞争。

死锁产生的条件和排查方式是什么?

死锁本质上是多个执行单元在持有部分资源的同时继续等待对方释放资源,导致所有相关线程都无法向前推进。面试回答要先讲清四个必要条件,再说明排查时如何从现象、线程状态、锁拥有关系、业务调用链和复现路径逐层定位,最后补充预防策略,例如统一加锁顺序、缩短锁范围、使用超时锁和减少共享可变状态。

进程、线程和协程有什么区别?

这道题本质是在考操作系统并发模型的分层理解:进程是资源隔离和资源分配的基本单位,线程是内核调度和 CPU 执行的基本单位,协程是用户态组织异步流程的轻量执行单元。面试时不能只背“进程重、线程轻、协程更轻”,要讲清地址空间、调度权、上下文切换成本、通信方式,以及在 I/O 密集、高并发服务中的取舍。

同题还出现在 1 个公司岗位

一个游戏场景题,多线程下更新分数排行榜?

这题核心不是选一个线程安全容器就结束,而是把排行榜拆成两个一致性问题:玩家当前分数的唯一真值,以及按分数排序的索引。多线程更新时,必须保证这两个结构的复合更新具备原子性,否则会出现丢分、重复排行项、旧分数残留、TopN 短暂错误等问题。一个成熟答案应先定义分数语义,再给出单机强一致方案,随后讨论 TopN、锁粒度、读写分离、分片、最终一致和测试验证。

CPU 负载很高但利用率很低,可能是什么原因?

CPU 负载很高但利用率很低,核心原因通常是:load average 统计的是正在运行或等待运行的任务,以及处于不可中断睡眠状态的任务;而 CPU utilization 只反映 CPU 真正在执行用户态、内核态等计算工作的时间占比。因此,很多进程卡在 D 状态、IO wait、磁盘或网络阻塞、锁等待、容器 CPU 配额限制等场景下,系统会表现为 load average 很高,但 CPU 使用率并不高。

针对登录界面设计case?

登录界面测试用例设计不能只停留在用户名密码正确或错误。一个测试开发视角的完整答案,应从业务入口、账号体系、认证链路、安全风控、状态保持、跳转权限、兼容性、性能、可用性、可观测性和自动化回归等维度展开。核心目标是验证用户能正确、安全、稳定地完成登录,同时防止越权、撞库、会话泄露、验证码绕过、弱交互导致误操作,以及高并发下认证服务不可用等问题。

怎么排查性能问题?

排查性能问题不能从单点经验出发,而要先把问题定义清楚:慢在哪里、影响谁、从什么时候开始、哪个指标异常、是否可复现。标准思路是先量化现象,再按调用链从客户端、网关、应用、缓存、数据库、网络和机器资源逐层缩小范围,结合压测、监控、日志、链路追踪、慢查询、线程栈、GC 日志、火焰图以及 CPU、内存、IO、网络等指标定位根因,最后通过修复验证和回归压测证明问题真正解决。

synchronized锁,以及其和juc包中的锁有什么区别、差异?

synchronized锁,以及其和juc包中的锁有什么区别、差异?这道腾讯牛客题的关键是围绕“synchronized 与 JUC 锁差异”讲清概念、机制、取舍和边界。synchronized 是 JVM 内置监视器锁,围绕对象 monitor、进入/退出临界区和锁优化工作;JUC 中 ReentrantLock 等锁通常基于 AQS,用 state、CAS 和等待队列实现更丰富的锁语义。

实现消息队列(多生产者,多消费者)?

实现消息队列(多生产者,多消费者)?这道腾讯牛客题的关键是围绕“多生产者多消费者消息队列”讲清概念、机制、取舍和边界。实现多生产者多消费者队列,要先确定有界还是无界、阻塞还是非阻塞、是否需要关闭语义和背压。经典阻塞队列可用 mutex + condition variable 保护环形缓冲区或链表队列。

如果定时任务多了单个线程处理不了,要使用多线程怎么处理?

如果定时任务多了单个线程处理不了,要使用多线程怎么处理?这道腾讯牛客题的关键是围绕“多线程任务调度与线程数配置”讲清概念、机制、取舍和边界。多线程处理任务要先把任务放进队列,再由线程池消费,避免无限创建线程。CPU 密集型线程数通常接近 CPU 核数或核数加一;I/O 密集型可以按 cores*(1+wait/compute) 估算,并通过压测校准。

如何实现多线程?

如何实现多线程?这道腾讯牛客题的关键是围绕“多线程实现方式”讲清概念、机制、取舍和边界。实现多线程要讲创建线程、任务执行、同步共享状态和生命周期收尾。C/C++ 常用 pthread_create 或 std::thread,Java 可用 Thread、Runnable、ExecutorService,Go 用 goroutine 但它是 runtime 调度的轻量协程。

要线程安全用什么Map?

要线程安全用什么Map?这道腾讯牛客题的关键是围绕“Java 线程安全 Map 选择”讲清概念、机制、取舍和边界。Java 线程安全 Map 通常优先考虑 ConcurrentHashMap。它通过分段或桶级别并发控制、CAS、volatile 和局部锁降低全表互斥成本,比 Hashtable 或 Collections.synchronizedMap 在高并发读写下更适合。

io密集型和cpu密集型如何分配线程,多分配线程数量还是少?

io密集型和cpu密集型如何分配线程,多分配线程数量还是少?这道腾讯牛客题的关键是围绕“CPU 密集与 I/O 密集线程数配置”讲清概念、机制、取舍和边界。CPU 密集型任务线程数通常接近 CPU 核数或 cores+1,避免过多线程争抢 CPU;I/O 密集型任务有大量等待时间,可以按 cores*(1+wait/compute) 估算更高线程数,再用压测校准。

为什么高并发场景下用unfair锁而不是fair?

为什么高并发场景下用unfair锁而不是fair?这道腾讯牛客题的关键是围绕“高并发下非公平锁取舍”讲清概念、机制、取舍和边界。高并发场景常用非公平锁,是因为它允许刚到达的线程在锁释放瞬间直接竞争成功,减少严格排队带来的线程唤醒、挂起和上下文切换成本。它牺牲一部分公平性,换取更高吞吐和更低平均调度开销。

fair锁如何实现的?

fair锁如何实现的?这道腾讯牛客题的关键是围绕“公平锁实现机制”讲清概念、机制、取舍和边界。公平锁的核心是按等待顺序授予锁,通常通过同步队列维护排队线程。以 ReentrantLock 公平模式为例,线程获取锁前会检查队列中是否已有前驱节点,有前驱就不插队,获取失败后进入 AQS 队列等待唤醒。

String与StringBuffer的区别,StringBuffer如何实现线程安全的?

String与StringBuffer的区别,StringBuffer如何实现线程安全的?这道腾讯牛客题的关键是围绕“String、StringBuffer 与线程安全”讲清概念、机制、取舍和边界。String 是不可变对象,拼接会产生新对象或由编译器优化;StringBuffer 是可变字符序列,核心方法带 synchronized,因此同一实例的修改具备互斥;StringBuilder 也是可变字符序列,但不做同步,单线程更快。

进程线程区别?

进程线程区别?这道腾讯牛客题的关键是围绕“进程与线程区别及上下文切换”讲清概念、机制、取舍和边界。进程是资源拥有和隔离单位,线程是同一进程内的调度执行单位。同一进程的线程共享地址空间、堆、全局变量和文件描述符,但有独立栈、寄存器上下文、线程局部存储和调度状态。

Java 中实现多线程有哪些方式?

Java 中实现多线程有哪些方式?这道腾讯牛客题的关键是围绕“Java 线程与多线程实现方式”讲清概念、机制、取舍和边界。Java 实现多线程可以从 Thread、Runnable、Callable/Future、线程池和 CompletableFuture 等方式回答。更底层看,Java 线程通常映射到操作系统原生线程,由 JVM 负责创建、调度协作、栈管理和状态维护。