0f68a64829
Deploy Examination / deploy (push) Successful in 10s
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
152 lines
10 KiB
JSON
152 lines
10 KiB
JSON
{
|
||
"topic": "go-java-concurrency",
|
||
"type": "true_false",
|
||
"schema_version": "1.0.0",
|
||
"generated": "2026-09-09T16:21:56+08:00",
|
||
"questions": [
|
||
{
|
||
"id": "tf-001",
|
||
"type": "true_false",
|
||
"difficulty": 2,
|
||
"tags": [
|
||
"go",
|
||
"channel"
|
||
],
|
||
"question": "在Go中,向已关闭的channel发送数据会导致panic,但从已关闭的channel接收数据会立即返回零值且ok为false。",
|
||
"answer": true,
|
||
"explanation": "Go中channel的close语义明确规定:向已关闭的channel发送数据会触发panic(send on closed channel),而从已关闭的channel接收数据不会阻塞,而是返回该元素类型的零值,第二个返回值ok为false。这是channel安全关闭的核心规则——关闭者负责发送端的停止,接收者只需检查ok值。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-002",
|
||
"type": "true_false",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"go",
|
||
"goroutine",
|
||
"pprof"
|
||
],
|
||
"question": "Go中goroutine泄漏最常见的原因之一是向无缓冲channel发送数据后,没有对应的接收者,导致发送方goroutine永远阻塞。",
|
||
"answer": true,
|
||
"explanation": "goroutine泄漏的本质是goroutine无法正常退出。向无缓冲channel发送时,如果没有goroutine在接收,发送方会永久阻塞。类似地,从无人发送的channel接收也会阻塞。其他常见泄漏场景包括:context未正确取消导致的无限等待循环、select中没有default分支且所有case都不就绪等。可以通过pprof的goroutine profile(runtime.NumGoroutine()或net/http/pprof)来定位泄漏的goroutine及其阻塞堆栈。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-003",
|
||
"type": "true_false",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"go",
|
||
"gmp"
|
||
],
|
||
"question": "Go的GMP调度模型中,当一个M(系统线程)发现本地P的本地队列为空时,会先尝试从全局队列获取G,再尝试从其他P的本地队列偷取(work-stealing)一半的G。",
|
||
"answer": true,
|
||
"explanation": "GMP调度模型中,M绑定P后优先从P的本地队列获取G执行。当本地队列为空时,调度器的窃取逻辑为:1)先检查全局队列(global run queue);2)再尝试netpoller(网络轮询器);3)最后从其他P的本地队列偷取(随机选择一个P,偷取其本地队列一半的G)。sysmon监控线程会定期检测长时间运行的G(抢占式调度),并将空闲P绑定到空闲M。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-004",
|
||
"type": "true_false",
|
||
"difficulty": 4,
|
||
"tags": [
|
||
"java",
|
||
"jmm",
|
||
"volatile"
|
||
],
|
||
"question": "在Java内存模型(JMM)中,volatile变量的写操作对后续所有线程的读操作都可见,因此volatile可以完全替代synchronized来保证复合操作的原子性。",
|
||
"answer": false,
|
||
"explanation": "volatile确实保证了可见性(写入后立即刷新到主内存,读取时从主内存加载)和有序性(禁止指令重排序),但它不保证复合操作的原子性。例如volatile int count; count++实质是读-改-写三步操作,多个线程并发执行时仍会出现竞态条件。对于复合操作的原子性,需要使用synchronized、ReentrantLock或java.util.concurrent.atomic包中的原子类(如AtomicInteger的CAS操作)。volatile的经典适用场景是状态标志位(boolean flag)和双重检查锁定(DCL)单例模式。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-005",
|
||
"type": "true_false",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"java",
|
||
"aqs",
|
||
"synchronized"
|
||
],
|
||
"question": "Java中AQS(AbstractQueuedSynchronizer)是ReentrantLock和Semaphore等同步器的核心实现基础,它通过CLH队列变体和volatile状态变量来实现线程排队与唤醒。",
|
||
"answer": true,
|
||
"explanation": "AQS是java.util.concurrent包的基石,其核心机制为:1)内部维护一个volatile int state变量表示同步状态;2)使用CLH(Craig, Landin, and Hagersten)队列变体实现线程排队,每个等待线程被封装为Node节点;3)获取锁失败的线程被包装为Node加入CLH队列尾部,然后通过LockSupport.park()阻塞;4)释放锁时通过unparkSuccessor()唤醒队列中的下一个线程。ReentrantLock通过state表示重入次数,Semaphore通过state表示可用许可数,CountDownLatch通过state表示倒计数值。而synchronized从JVM层面实现,不依赖AQS。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-006",
|
||
"type": "true_false",
|
||
"difficulty": 2,
|
||
"tags": [
|
||
"go",
|
||
"sync"
|
||
],
|
||
"question": "Go的sync.Once保证传入的函数只被执行一次,即使多个goroutine同时调用Do方法,也只有一个goroutine会执行该函数,其余goroutine会阻塞等待其完成。",
|
||
"answer": true,
|
||
"explanation": "sync.Once内部通过atomic.LoadInt32/StoreInt32检查并设置标志位来保证函数只执行一次。其Do方法的实现逻辑为:先通过原子操作检查done标志,若已执行则直接返回;若未执行,则通过Mutex加锁后再次检查(double-check)并执行函数。其他并发调用Do的goroutine会在Mutex上阻塞,直到执行的goroutine完成函数调用并释放锁。这保证了初始化代码的线程安全执行,是Go中实现懒初始化单例的标准方式。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-007",
|
||
"type": "true_false",
|
||
"difficulty": 4,
|
||
"tags": [
|
||
"java",
|
||
"forkjoinpool"
|
||
],
|
||
"question": "Java的ForkJoinPool采用工作窃取(work-stealing)算法,空闲的工作线程会从其他繁忙线程的任务队列尾部偷取任务执行,commonPool使用ForkJoinPool.defaultParallelism()作为默认并行度。",
|
||
"answer": true,
|
||
"explanation": "ForkJoinPool的核心设计是工作窃取:每个工作线程维护一个双端队列(deque),新任务从队列头部推入(push),本线程也从头部取出执行(LIFO,有利于局部性)。当某个线程的队列为空时,它会随机选择其他线程的队列,从其尾部窃取任务(FIFO,减少竞争)。commonPool是静态共享池,通过ForkJoinPool.commonPool()获取,默认并行度Runtime.getRuntime().availableProcessors()-1(当大于1时),适用于ParallelStream和CompletableFuture等场景。RecursiveTask(有返回值)和RecursiveAction(无返回值)是ForkJoinTask的两个子类,用于定义可分解的递归任务。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-008",
|
||
"type": "true_false",
|
||
"difficulty": 4,
|
||
"tags": [
|
||
"jmm",
|
||
"happens-before",
|
||
"go"
|
||
],
|
||
"question": "Go的内存模型中,goroutine的创建(go关键字)发生在该goroutine的执行之前,而channel的每次发送操作都happens-before对应的接收操作完成。",
|
||
"answer": false,
|
||
"explanation": "Go内存模型的happens-before规则中:1)goroutine的创建确实happens-before该goroutine的执行开始——这是正确的。2)但对于无缓冲channel:发送操作happens-before对应的接收操作完成;而对于有缓冲channel:第k次发送happens-before第k次接收的完成。关键区别在于'完成'——接收操作的完成是指接收到了数据,而不是仅仅开始接收。该题将两种channel混为一谈,表述不够准确,且channel的发送happens-before的是'对应的接收完成'而非'接收操作本身',存在语义偏差,因此判定为false。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-009",
|
||
"type": "true_false",
|
||
"difficulty": 3,
|
||
"tags": [
|
||
"java",
|
||
"thread"
|
||
],
|
||
"question": "Java中通过ThreadPoolExecutor创建线程池时,核心参数maximumPoolSize必须大于等于corePoolSize,当核心线程数未满时新任务会创建核心线程执行,核心线程满后任务会先进入工作队列,队列满后才创建非核心线程。",
|
||
"answer": true,
|
||
"explanation": "ThreadPoolExecutor的任务提交流程严格遵循以下顺序:1)当前线程数 < corePoolSize → 创建新核心线程执行任务;2)核心线程满 → 将任务放入workQueue(工作队列)等待;3)队列已满且线程数 < maximumPoolSize → 创建非核心线程执行任务;4)线程数已达maximumPoolSize且队列满 → 执行拒绝策略(AbortPolicy/CallerRunsPolicy/DiscardPolicy/DiscardOldestPolicy)。maximumPoolSize < corePoolSize在语义上不成立,构造时虽然不会抛异常,但实际线程数永远不会超过corePoolSize。注意:如果使用无界队列(如LinkedBlockingQueue),maximumPoolSize实际上不会生效。",
|
||
"source": null,
|
||
"related": []
|
||
},
|
||
{
|
||
"id": "tf-010",
|
||
"type": "true_false",
|
||
"difficulty": 2,
|
||
"tags": [
|
||
"java",
|
||
"jfr"
|
||
],
|
||
"question": "Java Flight Recorder(JFR)是JDK内置的低开销性能分析工具,通过事件采样的方式记录CPU、内存、I/O、线程等运行时信息,可通过jcmd或jfr命令行工具启动录制,也可在代码中通过Recording API编程控制。",
|
||
"answer": true,
|
||
"explanation": "JFR是Oracle JDK和OpenJDK(JEP 171/328/336)内置的生产级监控框架,设计目标是开销低于1%。其核心特点包括:1)基于事件驱动的采样机制,预定义了数百种事件类型(如jdk.CPULoad、jdk.GarbageCollection、jdk.ThreadStart等);2)支持命令行启动(jcmd <pid> JFR.start、jfr start/stop/dump)和编程方式(new Recording().start());3)录制数据以.jfr文件存储,可通过JDK Mission Control(JMC)可视化分析,生成火焰图等;4)从JDK 11起可免费用于生产环境(此前商业特性)。JFR与异步采样profiling配合,能有效定位CPU热点、锁竞争、GC停顿等问题。",
|
||
"source": null,
|
||
"related": []
|
||
}
|
||
]
|
||
} |