Files
examination/topics/interview-prep/go-java-concurrency/single_choice.json
T
wonder 0f68a64829
Deploy Examination / deploy (push) Successful in 10s
feat: add interview-prep topic group with 250 questions (6 subtopics, 5 question types)
Subtopics:
- distributed-microservice: 45 questions (分布式微服务架构)
- message-queue: 45 questions (消息队列)
- k8s-observability: 45 questions (K8s与可观测性)
- go-java-concurrency: 45 questions (Go/Java并发模型)
- database-advanced: 35 questions (数据库进阶)
- ai-engineering: 35 questions (AI工程实践)

Question types: single_choice, true_false, fill_blank, short_answer, code_reading
2026-09-09 16:36:27 +08:00

332 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"topic": "go-java-concurrency",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "2026-09-09T16:21:56+08:00",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 2,
"tags": [
"Go",
"GMP",
"调度模型"
],
"question": "在 Go 的 GMP 调度模型中,P(Processor)代表什么?",
"options": {
"A": "操作系统线程(OS Thread)",
"B": "逻辑处理器,持有本地运行队列和资源上下文",
"C": "用户级轻量级线程(goroutine)",
"D": "内存管理单元(MMU)"
},
"answer": "B",
"explanation": "P(Processor)是逻辑处理器,不是物理 CPU 核心。P 持有一个本地 goroutine 运行队列(local run queue)和执行 goroutine 所需的资源上下文(如 mcache、span 等)。G 是 goroutine,M 是操作系统线程。默认情况下 P 的数量等于 GOMAXPROCS,默认值为 CPU 核心数。P 在 G、M 之间起桥梁作用,G 绑定到 P 的本地队列上运行,M 则需要绑定一个 P 才能执行 G。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 3,
"tags": [
"Go",
"GMP",
"work-stealing"
],
"question": "关于 Go GMP 调度器的 work-stealing 机制,以下描述正确的是?",
"options": {
"A": "当某个 P 的本地队列为空时,它会从其他 P 的本地队列中窃取一半的 goroutine",
"B": "当某个 M 空闲时,它会随机选择一个 P 并抢占其正在运行的 goroutine",
"C": "work-stealing 仅在全局队列为空时才会触发",
"D": "每次窃取操作都会从目标 P 的队列头部取走一个 goroutine"
},
"answer": "A",
"explanation": "当一个 P 的本地队列为空且全局队列也为空时,调度器会执行 work-stealing:随机选择另一个 P,将其本地队列中的 goroutine 偷走一半。这种设计保证了负载均衡——不会出现某个 P 忙碌而另一个 P 空闲的情况。选项 C 错误,work-stealing 的前提是本地队列为空,然后先尝试全局队列,再尝试 work-stealing。选项 D 错误,窃取时是取走一半(约 half),而非单个。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Go",
"GMP",
"sysmon",
"netpoller"
],
"question": "Go 运行时中 sysmon 线程的主要职责不包括以下哪项?",
"options": {
"A": "检测并抢占长时间运行的 goroutine",
"B": "回收超过 10ms 未使用的内存堆栈",
"C": "将因系统调用阻塞的 M 上的 P 抢占并绑定到空闲 M",
"D": "编译 Go 源码中的泛型函数"
},
"answer": "D",
"explanation": "sysmon 是一个特殊的后台 M(不绑定 P),其职责包括:1)抢占运行超过 10ms 的 goroutine(基于协作+信号的抢占式调度);2)将阻塞在系统调用上的 M 的 P 抢占出来绑定到空闲 M 上,保证 P 不被浪费;3)触发 GC 的后台标记;4)回收长时间未使用的堆栈。编译工作由编译器在编译期完成,与 sysmon 无关。netpoller 也与 sysmon 有协作关系,用于处理网络 I/O 的轮询。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 2,
"tags": [
"Go",
"channel",
"hchan"
],
"question": "在 Go 中,一个无缓冲 channel(unbuffered channel)的发送操作何时完成?",
"options": {
"A": "将值放入 channel 缓冲区后立即返回",
"B": "必须有另一个 goroutine 同时执行接收操作,两者同步后才完成",
"C": "无论是否有接收方,发送操作都会立即返回",
"D": "发送操作会阻塞直到 channel 被关闭"
},
"answer": "B",
"explanation": "无缓冲 channel(make(chan T))是同步通道,发送方在接收方准备好之前会阻塞。只有当另一个 goroutine 执行了接收操作,发送和接收才会同步完成。这就是 Go 的'通信通过共享内存'而非'共享内存通过通信'的核心体现。有缓冲 channel(make(chan T, n))在缓冲区未满时发送不会阻塞,这是两者的关键区别。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Go",
"channel",
"hchan",
"底层结构"
],
"question": "Go channel 的底层数据结构 hchan 中,sendx 和 recvx 字段的作用是什么?",
"options": {
"A": "记录 channel 的发送和接收总次数,用于性能统计",
"B": "环形缓冲区中下一个发送/接收位置的索引,用于实现 FIFO 语义",
"C": "记录当前正在阻塞的发送者/接收者数量",
"D": "指向 channel 类型元信息的指针偏移量"
},
"answer": "B",
"explanation": "hchan 是 Go channel 的底层结构体。其中 buf 是一个环形缓冲区(用于有缓冲 channel),sendx 和 recvx 分别记录下一次发送和接收在 buf 中的索引位置,实现 FIFO(先进先出)语义。当 sendx 或 recvx 到达缓冲区末尾时会回绕到 0。与之相关的字段还有 sendq(等待发送的 goroutine 队列,即 sudog 链表)和 recvq(等待接收的 goroutine 队列)。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 2,
"tags": [
"Go",
"channel",
"close"
],
"question": "关于 Go channel 的 close 语义,以下哪项描述是正确的?",
"options": {
"A": "对一个已关闭的 channel 再次调用 close 会 panic",
"B": "关闭一个 nil channel 会静默忽略,不会 panic",
"C": "关闭 channel 后,仍然可以向其发送数据",
"D": "关闭 channel 后,接收操作会立即阻塞"
},
"answer": "A",
"explanation": "Go 对 channel 的关闭有严格的规则:1)对已关闭的 channel 再次 close 会触发 panic(send on closed channel 的同类保护);2)对 nil channel 调用 close 同样会 panic;3)关闭 channel 后不能再发送数据(会 panic),但可以继续接收——接收操作会返回缓冲区中的剩余数据,缓冲区读完后接收操作返回零值且 ok 为 false;4)只有发送方或唯一所有者应该关闭 channel,不应由接收方关闭。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 3,
"tags": [
"Go",
"sync",
"Mutex",
"RWMutex"
],
"question": "Go 的 sync.RWMutex 相比 sync.Mutex 的主要优势是什么?",
"options": {
"A": "在写锁模式下性能更好",
"B": "允许多个 goroutine 同时持有读锁,提高读多写少场景的并发性能",
"C": "支持可重入锁,同一个 goroutine 可多次加锁",
"D": "使用无锁算法实现,完全不涉及操作系统原语"
},
"answer": "B",
"explanation": "RWMutex 提供了读写分离的锁机制:多个 goroutine 可以同时持有 RLock(读锁),只有写锁是互斥的。这在读多写少的场景下显著优于 Mutex(所有操作互斥)。选项 A 错误,RWMutex 的写锁性能通常略低于纯 Mutex,因为需要维护读写计数。选项 C 错误,Go 的 Mutex 和 RWMutex 都不是可重入的,重复加锁会导致死锁。选项 D 错误,底层仍使用原子操作和信号量。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 3,
"tags": [
"Go",
"sync",
"Once",
"Pool"
],
"question": "关于 Go 的 sync.Once 和 sync.Pool,以下哪项描述是正确的?",
"options": {
"A": "sync.Once 的 Do 方法在并发调用时,只有第一个调用会执行传入的函数,其余阻塞等待",
"B": "sync.Pool 中的对象永远不会被回收,适合存放全局唯一实例",
"C": "sync.Once 的 Do 方法中如果传入的函数 panic,后续调用会重新执行该函数",
"D": "sync.Pool 是线程安全的,但不支持跨 goroutine 共享对象"
},
"answer": "A",
"explanation": "sync.Once 的 Do(f) 保证 f 只被执行一次,即使多个 goroutine 并发调用。第一个调用者执行 f,其他调用者阻塞等待 f 完成后直接返回。选项 B 错误,Pool 中的对象在每次 GC 时可能被清除(Go 1.13 后有 victim cache 机制,延迟一个 GC 周期),不适合存放必须持久化的对象。选项 C 错误,如果 f panic,Once 仍然认为已执行过,后续调用不会再执行 f。选项 D 错误,Pool 设计上就是跨 goroutine 共享的,用于对象复用以减少 GC 压力。",
"source": null,
"related": []
},
{
"id": "sc-009",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Go",
"sync",
"Map",
"并发"
],
"question": "Go 的 sync.Map 适合以下哪种使用场景?",
"options": {
"A": "频繁写入、少量读取的场景",
"B": "key 集合稳定,大量并发读取、极少写入的场景",
"C": "需要有序遍历所有 key-value 的场景",
"D": "需要支持批量删除操作的场景"
},
"answer": "B",
"explanation": "sync.Map 针对两种场景优化:1)key 集合稳定后只读(read-only);2)多个 goroutine 读写不同的 key(disjoint key sets)。它内部使用 read(只读,无锁访问)和 dirty(写锁保护)两个 map 实现读写分离,在大量并发读的场景下性能远优于 Mutex+map。选项 A 错误,频繁写入会导致 read/dirty 频繁同步,性能反而不如加锁。选项 C 错误,sync.Map 不保证遍历顺序。选项 D 错误,sync.Map 不提供批量删除接口。",
"source": null,
"related": []
},
{
"id": "sc-010",
"type": "single_choice",
"difficulty": 3,
"tags": [
"Go",
"内存模型",
"happens-before",
"race"
],
"question": "Go 的 -race 检测器主要基于什么技术实现?",
"options": {
"A": "静态代码分析,在编译期检测所有潜在的数据竞争",
"B": "AddressSanitizer(ASan),在运行时检测内存越界访问",
"C": "ThreadSanitizer(TSan),在运行时动态追踪内存访问的 happens-before 关系",
"D": "在每个内存访问处插入互斥锁,通过死锁检测间接发现竞争"
},
"answer": "C",
"explanation": "Go 的 race detector 基于 Google 的 ThreadSanitizer(TSan)技术,在编译期插桩(instrumentation)并在运行时动态追踪所有内存访问和同步操作,构建 happens-before 关系图。如果两个并发访问(至少一个是写)之间没有 happens-before 关系,就报告数据竞争。选项 A 错误,它不是纯静态分析,需要运行时执行才能检测。选项 B 错误,ASan 检测的是内存安全(越界、use-after-free),不是数据竞争。选项 D 完全不正确。",
"source": null,
"related": []
},
{
"id": "sc-011",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Go",
"goroutine泄漏",
"pprof",
"context"
],
"question": "以下哪种情况最容易导致 goroutine 泄漏?",
"options": {
"A": "goroutine 中执行了一次短暂的 CPU 计算后正常返回",
"B": "goroutine 向一个没有接收者的无缓冲 channel 发送数据",
"C": "goroutine 调用了 time.Sleep 后正常退出",
"D": "goroutine 中使用了 defer 语句"
},
"answer": "B",
"explanation": "goroutine 泄漏是指 goroutine 无法正常退出,持续占用内存和调度资源。向无缓冲 channel 发送数据时,如果没有接收者,发送操作会永久阻塞,goroutine 就泄漏了。常见的泄漏模式还包括:1)从无人发送的 channel 接收;2)未设置超时或取消的阻塞操作(如未用 context 控制的 HTTP 请求);3)无限循环且无退出条件。排查工具:pprof 的 goroutine profile 可以看到所有存活 goroutine 的堆栈,定位泄漏源。",
"source": null,
"related": []
},
{
"id": "sc-012",
"type": "single_choice",
"difficulty": 3,
"tags": [
"Java",
"线程",
"ThreadPoolExecutor"
],
"question": "Java ThreadPoolExecutor 的核心参数 corePoolSize、maximumPoolSize、workQueue 之间的执行关系是?",
"options": {
"A": "任务提交后直接创建线程,不受 corePoolSize 限制",
"B": "先创建到 corePoolSize 个线程 → 放入 workQueue → 满了才创建到 maximumPoolSize 个线程",
"C": "先创建到 maximumPoolSize 个线程 → 放入 workQueue → 满了才缩减到 corePoolSize",
"D": "workQueue 仅在 corePoolSize 和 maximumPoolSize 都满时才使用"
},
"answer": "B",
"explanation": "ThreadPoolExecutor 的任务处理流程:1)当前线程数 < corePoolSize → 直接创建新线程执行;2)线程数 ≥ corePoolSize → 放入 workQueue 等待;3)workQueue 已满且线程数 < maximumPoolSize → 创建新线程执行;4)线程数 ≥ maximumPoolSize 且队列满 → 执行拒绝策略(RejectedExecutionHandler)。keepAliveTime 控制非核心线程的空闲存活时间。",
"source": null,
"related": []
},
{
"id": "sc-013",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Java",
"锁机制",
"synchronized",
"锁升级"
],
"question": "Java synchronized 的锁升级过程是怎样的?",
"options": {
"A": "无锁 → 轻量级锁 → 偏向锁 → 重量级锁",
"B": "偏向锁 → 轻量级锁 → 重量级锁(不可逆)",
"C": "无锁 → 偏向锁 → 轻量级锁 → 重量级锁",
"D": "轻量级锁 → 重量级锁 → 偏向锁(竞争结束后降级)"
},
"answer": "C",
"explanation": "JVM 对 synchronized 的优化采用逐步升级策略:1)无锁状态 → 首次进入同步块时,如果对象头 Mark Word 未偏向任何线程,升级为偏向锁(仅记录线程 ID,无 CAS 开销);2)当第二个线程尝试竞争时,偏向锁撤销,升级为轻量级锁(通过 CAS 将 Mark Word 替换为指向栈帧中 Lock Record 的指针);3)轻量级锁竞争失败(自旋超过一定次数),膨胀为重量级锁(OS mutex,未获锁的线程阻塞挂起)。注意:偏向锁在 JDK 15 后默认关闭,因为其撤销成本较高。",
"source": null,
"related": []
},
{
"id": "sc-014",
"type": "single_choice",
"difficulty": 4,
"tags": [
"Java",
"JMM",
"volatile",
"happens-before"
],
"question": "在 Java 内存模型(JMM)中,volatile 变量的 happens-before 语义是?",
"options": {
"A": "对 volatile 变量的写操作 happens-before 后续对同一变量的读操作",
"B": "volatile 仅保证变量的原子性,不保证可见性",
"C": "volatile 读写操作不会插入内存屏障",
"D": "volatile 变量的写 happens-before 所有后续的读写操作"
},
"answer": "A",
"explanation": "根据 JMM 的 happens-before 八大规则之一:volatile 变量规则——对 volatile 字段的写操作 happens-before 后续对同一字段的读操作。具体实现上,volatile 写后插入 StoreStore + StoreLoad 屏障,volatile 读前插入 LoadLoad + LoadStore 屏障。选项 B 错误,volatile 不保证原子性(如 i++ 不是原子操作),但保证可见性。选项 C 错误,volatile 正是通过内存屏障实现的。选项 D 错误,happens-before 是针对同一变量的特定读写对,不是全局的。",
"source": null,
"related": []
},
{
"id": "sc-015",
"type": "single_choice",
"difficulty": 2,
"tags": [
"Java",
"ForkJoinPool",
"工作窃取"
],
"question": "Java ForkJoinPool 的工作窃取(work-stealing)机制是指?",
"options": {
"A": "空闲线程从全局任务队列中取任务执行",
"B": "空闲线程从其他忙碌线程的本地任务队列尾部窃取任务执行",
"C": "主线程将任务平均分配给所有工作线程",
"D": "工作线程按固定顺序依次执行所有提交的任务"
},
"answer": "B",
"explanation": "ForkJoinPool 采用工作窃取算法:每个工作线程维护自己的双端队列(deque),从头部取任务执行(FIFO);当自己的队列为空时,从其他忙碌线程的队列尾部窃取任务(LIFO),减少竞争。这种设计比传统的线程池(单个共享队列)更高效,因为减少了锁争用。ForkJoinPool 是 parallelStream() 的底层线程池,commonPool 是共享的 ForkJoinPool 实例。RecursiveTask(有返回值)和 RecursiveAction(无返回值)是 fork/join 编程模型的任务基类。",
"source": null,
"related": []
}
]
}