Files
examination/topics/interview-prep/go-java-concurrency/fill_blank.json
T

186 lines
12 KiB
JSON
Raw Normal View History

{
"topic": "go-java-concurrency",
"type": "fill_blank",
"schema_version": "1.0.0",
"generated": "2026-09-09T16:21:56+08:00",
"questions": [
{
"id": "fb-001",
"type": "fill_blank",
"difficulty": 2,
"tags": [
"go",
"gmp"
],
"question": "Go语言GMP调度模型中,P是G和M之间的调度上下文,当一个P的本地队列已满时,新创建的G会被放入______队列,由其他P通过work-stealing机制窃取执行。",
"answer": [
"全局",
"全局队列",
"global queue",
"global"
],
"explanation": "GMP模型中,每个P拥有一个本地可运行G队列(local run queue)。当本地队列容量(默认256)满载时,新创建的G会被放入全局队列(global queue)。调度器会在以下时机从全局队列取出G:1)本地队列为空时;2)每调度61次从全局队列取一个G,防止全局队列中的G被饿死。work-stealing则是当某个P的本地队列为空时,它会尝试从其他P的本地队列窃取一半的G来运行,从而实现负载均衡。",
"source": null,
"related": []
},
{
"id": "fb-002",
"type": "fill_blank",
"difficulty": 2,
"tags": [
"java",
"thread"
],
"question": "Java中创建线程的三种主要方式分别是继承______类、实现Runnable接口和实现Callable接口。",
"answer": [
"Thread"
],
"explanation": "Java提供三种创建线程的方式:1)继承Thread类,重写run()方法,直接调用start()启动;2)实现Runnable接口,将任务与线程解耦,传入Thread构造器;3)实现Callable接口并配合FutureTask,支持返回结果和抛出异常。其中Runnable的run()没有返回值且不能抛出受检异常,而Callable的call()可以返回泛型结果。实际开发中,推荐使用线程池(ExecutorService)而非直接创建线程,以实现资源复用和更好的并发控制。",
"source": null,
"related": []
},
{
"id": "fb-003",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"go",
"channel"
],
"question": "Go的channel底层数据结构hchan中,环形缓冲区______用于存储有缓冲channel中待发送的数据,sendx和recvx分别指向其发送和接收位置。",
"answer": [
"buf",
"ringbuf"
],
"explanation": "hchan是Go channel的底层实现结构,核心字段包括:buf(环形数组缓冲区,仅用于有缓冲channel)、sendx和recvx(分别标记环形缓冲区的发送和接收位置,用于实现FIFO)、sendq和recvq(sudog链表,分别记录因发送/接收阻塞的goroutine)。当channel无缓冲时,buf为nil,发送和接收直接在两个goroutine之间传递数据。环形缓冲区的设计使得sendx到达末尾后可以回绕到数组起始位置,高效利用内存。",
"source": null,
"related": []
},
{
"id": "fb-004",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"java",
"volatile",
"jmm"
],
"question": "Java内存模型中,volatile变量保证可见性的核心原理是:对volatile变量的写操作会立即刷新到______,读操作会从该处重新加载,从而禁止了线程工作内存对该变量的缓存。",
"answer": [
"主内存",
"主存",
"main memory"
],
"explanation": "Java内存模型(JMM)定义了主内存(main memory)和工作内存(working memory)的抽象概念。每个线程拥有自己的工作内存(对应CPU缓存或寄存器),对变量的读写首先在工作内存中进行,再同步到主内存。volatile关键字的关键语义包括:1)保证可见性——写volatile变量时立即刷新到主内存,读时从主内存重新加载;2)禁止指令重排序——通过内存屏障(Memory Barrier)确保volatile写之前的操作不会被重排到写之后,volatile读之后的操作不会被重排到读之前。但注意volatile不能保证原子性,例如i++对volatile变量的自增仍然不是线程安全的。",
"source": null,
"related": []
},
{
"id": "fb-005",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"go",
"sync"
],
"question": "Go的sync.WaitGroup有三个核心方法:Add用于增加计数器,Done用于减少计数器(等同于Add(-1)),当计数器归零时,调用______方法阻塞等待的goroutine会被唤醒。",
"answer": [
"Wait"
],
"explanation": "WaitGroup是Go中用于等待一组goroutine完成的同步原语。典型用法:主goroutine在启动工作goroutine前调用Add(n)设置计数器,每个工作goroutine完成时调用Done()(内部执行Add(-1)),主goroutine调用Wait()阻塞直到计数器归零。底层实现中,WaitGroup使用原子操作维护state1和state2两个字段,分别存储信号量和计数器值。需要注意的是:Add的调用必须在Wait之前或在Wait的goroutine内部调用,不能在工作goroutine中调用Add,否则可能在Wait已经开始等待后才Add,导致竞态条件。",
"source": null,
"related": []
},
{
"id": "fb-006",
"type": "fill_blank",
"difficulty": 3,
"tags": [
"java",
"synchronized",
"cas"
],
"question": "Java synchronized关键字在JDK 6之后引入了锁升级机制,其升级路径为:无锁 → ______ → 轻量级锁(自旋) → 重量级锁,这一优化大幅提升了在不同竞争程度下的同步性能。",
"answer": [
"偏向锁",
"biased locking",
"Biased Locking"
],
"explanation": "JDK 6引入的锁升级(lock escalation)是synchronized性能优化的关键:1)偏向锁——当只有一个线程访问同步块时,在对象头的Mark Word中记录线程ID,后续该线程进入时只需CAS检查ID,无需任何同步操作;2)轻量级锁——当第二个线程尝试获取锁时,偏向锁撤销,升级为轻量级锁,通过CAS将Mark Word复制到栈帧的Lock Record中,适合短时间、低竞争的场景,未获取到锁的线程会通过自旋(adaptive spinning)等待;3)重量级锁——当自旋超过一定次数或竞争激烈时,升级为重量级锁,依赖操作系统的Mutex Lock实现,未获取到锁的线程会被阻塞挂起。锁只能升级不能降级(JDK 15默认关闭了偏向锁)。",
"source": null,
"related": []
},
{
"id": "fb-007",
"type": "fill_blank",
"difficulty": 4,
"tags": [
"go",
"gmp"
],
"question": "GMP调度模型中,当goroutine执行系统调用(如文件I/O)被阻塞时,M会与P解绑,P会被交给其他空闲的M或创建新的M来继续执行本地队列中的G,这一机制被称为______。",
"answer": [
"hand off",
"handoff",
"hand-off"
],
"explanation": "Hand off(移交)是GMP调度模型中处理M阻塞的核心机制。当某个M上的G执行阻塞性系统调用时,M会进入阻塞状态,此时P(及其本地队列中的G)不能跟着一起等待。调度器会将P从这个阻塞的M上解绑(hand off),交给另一个空闲的M来继续执行P本地队列中的G。如果此时没有空闲的M,则会创建一个新M。当系统调用返回后,原M会尝试获取一个空闲的P来继续运行,如果没有空闲P,则该G会被放入全局队列。这一机制确保了一个阻塞的系统调用不会导致整个P上的所有goroutine都被阻塞,提高了调度效率。",
"source": null,
"related": []
},
{
"id": "fb-008",
"type": "fill_blank",
"difficulty": 4,
"tags": [
"java",
"aqs"
],
"question": "Java并发包中的AQS(AbstractQueuedSynchronizer)内部维护了一个volatile int类型的______变量和一个双向链表实现的FIFO队列,ReentrantLock、Semaphore、CountDownLatch等同步器都基于AQS实现。",
"answer": [
"state",
"state变量"
],
"explanation": "AQS是java.util.concurrent包的核心框架,采用模板方法模式,子类通过重写tryAcquire/tryRelease(独占模式)或tryAcquireShared/tryReleaseShared(共享模式)来实现不同的同步语义。核心机制:1)state变量——volatile修饰的整数,不同同步器赋予不同含义:ReentrantLock中表示重入次数(0为未锁定),Semaphore中表示可用许可数,CountDownLatch中表示倒计数值;2)CLH队列——基于双向链表实现的FIFO等待队列,节点封装了等待线程和等待状态;3)acquire/release流程——线程获取锁失败时被封装为节点加入队列尾部并自旋或park阻塞,前驱节点释放后通过unpark唤醒后继节点。",
"source": null,
"related": []
},
{
"id": "fb-009",
"type": "fill_blank",
"difficulty": 4,
"tags": [
"java",
"forkjoinpool"
],
"question": "Java ForkJoinPool采用工作窃取算法,每个工作线程维护一个______队列(deque),当线程自己的任务队列为空时,它会从其他线程队列的尾部窃取任务执行,以此实现负载均衡。",
"answer": [
"双端队列",
"deque",
"Deque",
"双端",
"work-stealing queue"
],
"explanation": "ForkJoinPool是ExecutorService的特殊实现,专为分治任务(divide-and-conquer)设计。其核心设计:1)双端队列(Deque)——每个worker线程拥有自己的双端队列,本地任务从头部push/pop(LIFO,利用缓存局部性),窃取时从其他线程队列的尾部pop(减少竞争);2)工作窃取(work-stealing)——空闲线程从其他忙碌线程的队列尾部窃取任务,避免线程空闲;3)任务拆分——继承RecursiveTask(有返回值)或RecursiveAction(无返回值),通过fork()将子任务推入自己的队列,join()等待子任务结果;4)ForkJoinPool.commonPool()——JDK 8+提供默认共享池,parallelStream()底层使用它。任务粒度过细会导致调度开销过大,过粗则无法充分利用并行性。",
"source": null,
"related": []
},
{
"id": "fb-010",
"type": "fill_blank",
"difficulty": 5,
"tags": [
"go",
"pprof"
],
"question": "Go的pprof工具支持多种profile类型:CPU profile记录程序在各位置的采样频率,heap profile记录内存分配,block profile记录goroutine在同步原语上的阻塞时间,而______ profile专门记录goroutine的调用栈,用于排查goroutine泄漏问题。",
"answer": [
"goroutine",
"goroutine profile"
],
"explanation": "pprof是Go内置的强大性能分析工具,支持的profile类型各有用途:1)CPU profile——通过定时采样(默认100Hz)记录CPU使用热点,用于发现CPU密集型瓶颈;2)heap profile——记录堆内存的分配和持有情况,可用于定位内存泄漏和过度分配;3)block profile——记录goroutine在sync.Mutex、channel等同步原语上阻塞的时长和次数,用于发现锁竞争瓶颈;4)goroutine profile——列出所有活跃goroutine的调用栈和状态(running/waiting/sleeping等),是排查goroutine泄漏的关键工具。当goroutine泄漏时,该profile会显示大量goroutine堆积在同一个调用栈上(如等待永远不会接收到数据的channel)。定位后通常需要检查:未关闭的channel、未取消的context、死循环中缺少退出条件等问题。可通过runtime/pprof包或net/http/pprof端点(/debug/pprof/)获取profile数据,再用go tool pprof进行可视化分析或生成火焰图。",
"source": null,
"related": []
}
]
}