Files
examination/topics/thumbup/distributed-lock/single_choice.json
T

168 lines
9.6 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": "distributed-lock",
"type": "single_choice",
"schema_version": "1.0.0",
"generated": "2026-09-09T21:42:00+08:00",
"questions": [
{
"id": "sc-001",
"type": "single_choice",
"difficulty": 1,
"tags": [
"distributed-lock",
"redis-lock"
],
"question": "在 RedisLockUtil 中,setIfAbsent 方法的底层对应的 Redis 命令是什么?",
"options": {
"A": "SET key value",
"B": "SETNX key value",
"C": "SET key value NX EX ttl",
"D": "INCR key"
},
"answer": "C",
"explanation": "Spring Data Redis 的 setIfAbsent(key, value, timeout, unit) 底层会拼接 NX(不存在才设置)和 EX/PX(过期时间)参数,等价于 Redis 的 SET key value NX EX ttl 原子命令。SETNX 命令(B 选项)是 Redis 2.6.12 之前的旧方式,不具备原子性地设置过期时间的能力;单纯 SET(A 选项)不带 NX 参数会直接覆盖已有值。",
"source": null,
"related": []
},
{
"id": "sc-002",
"type": "single_choice",
"difficulty": 2,
"tags": [
"distributed-lock",
"redis-lock"
],
"question": "RedisLockUtil 中 unlock 方法使用 Lua 脚本的核心原因是什么?",
"options": {
"A": "提高执行性能,减少网络往返",
"B": "保证 get 和 del 操作的原子性,防止误删其他线程的锁",
"C": "避免 Redis 主从切换导致锁丢失",
"D": "支持可重入锁的实现"
},
"answer": "B",
"explanation": "unlock 中的 Lua 脚本先 GET 锁的值,与当前线程持有的 lockValue 比较,匹配才 DEL。如果不使用 Lua 脚本,GET 和 DEL 是两条独立命令,在两步之间锁可能已过期并被其他线程获取,此时 DEL 会误删新线程的锁。Lua 脚本在 Redis 中是原子执行的,消除了这个竞态条件。选项 C 是 Redis 锁的已知局限(红锁解决),不是使用 Lua 脚本的原因;D 需要额外设计,不是 Lua 脚本本身的功能。",
"source": null,
"related": []
},
{
"id": "sc-003",
"type": "single_choice",
"difficulty": 2,
"tags": [
"distributed-lock",
"synchronized"
],
"question": "旧方案 synchronized + String.intern() 的主要缺陷是什么?",
"options": {
"A": "String.intern() 会污染永久代/元空间,导致 OOM",
"B": "synchronized 无法跨 JVM 进程工作,在分布式部署下无效",
"C": "String.intern() 返回的是不可变引用,无法释放锁",
"D": "synchronized 是公平锁,性能极差"
},
"answer": "B",
"explanation": "synchronized 是 JVM 级别的内置锁,只能在同一 JVM 进程内的线程之间互斥。在分布式部署(多实例)场景下,不同 JVM 中的线程持有各自的 monitor,无法实现全局互斥,可能导致重复点赞等并发问题。虽然 A 选项(intern 污染字符串常量池)也是一个缺点,但它不是迁移到分布式锁的根本原因。C 选项错误,intern 返回的字符串可以正常 GC。D 选项错误,synchronized 默认是非公平锁。",
"source": null,
"related": []
},
{
"id": "sc-004",
"type": "single_choice",
"difficulty": 3,
"tags": [
"distributed-lock",
"lock-design"
],
"question": "RedisLockUtil 中的锁超时时间(DEFAULT_LOCK_TIMEOUT = 10000L)的作用是什么?如果业务执行超过该时间会发生什么?",
"options": {
"A": "锁永远不会释放,需要手动调用 unlock",
"B": "Redis 自动删除锁 key,其他线程可获取锁,当前线程仍持有 lockValue 但实际已失去保护",
"C": "Redis 会向当前线程发送中断信号",
"D": "锁会自动续期,直到业务完成"
},
"answer": "B",
"explanation": "DEFAULT_LOCK_TIMEOUT 是 Redis 锁 key 的过期时间(TTL),由 SET 命令的 EX 参数控制。当业务执行时间超过该 TTL,Redis 会自动删除锁 key,此时其他线程可以成功获取锁,破坏互斥性。而当前线程的 ThreadLocal 中仍保留着 lockValue,但该值在 Redis 中已不存在,Lua 脚本的 get 比较会失败。这正是没有看门狗(watchdog)机制的经典问题——可参考 Redisson 的自动续期方案。该实现没有锁续期机制。",
"source": null,
"related": []
},
{
"id": "sc-005",
"type": "single_choice",
"difficulty": 3,
"tags": [
"distributed-lock",
"lock-design"
],
"question": "tryLock 方法中使用 ThreadLocal 存储 lockValue 的设计有什么潜在问题?",
"options": {
"A": "ThreadLocal 存储的值会被其他线程读取,导致数据竞争",
"B": "如果 tryLock 成功但 unlock 未被调用(异常路径),ThreadLocal 不会被清理,可能导致线程池复用时锁泄露",
"C": "ThreadLocal 不支持序列化,无法在分布式环境使用",
"D": "ThreadLocal 会在每次 set 时触发 GC,影响性能"
},
"answer": "B",
"explanation": "executeWithLock 方法虽然在 finally 中调用了 unlock(会清理 ThreadLocal),但如果直接调用 tryLock 后忘记调用 unlock,或者中间发生未捕获的异常导致 unlock 未执行,ThreadLocal 中的 lockValue 就不会被 remove。在使用线程池的场景下,线程被复用时旧的 lockValue 仍在,可能导致后续锁操作使用错误的值。虽然 executeWithLock 封装了 try/finally,但作为一个独立工具类,这仍是设计上的隐患。实际生产中通常将 lockValue 作为方法返回值而非存储在 ThreadLocal 中。",
"source": null,
"related": []
},
{
"id": "sc-006",
"type": "single_choice",
"difficulty": 4,
"tags": [
"distributed-lock",
"redis-lock"
],
"question": "在 Redis 主从架构下,RedisLockUtil 的 tryLock 方法存在什么已知缺陷?",
"options": {
"A": "主节点宕机后从节点提升为主,但锁数据可能未同步,导致多个客户端同时获得锁",
"B": "从节点不支持 SETNX 命令",
"C": "主从同步延迟会导致 setIfAbsent 返回 false 但实际已写入",
"D": "哨兵模式下无法连接 Redis"
},
"answer": "A",
"explanation": "这是 Redis 单节点/主从模式分布式锁的经典问题(Martin Kleppmann 在分析 Redisson 时指出):客户端 A 在主节点写入锁 key,但主节点在同步到从节点之前宕机;从节点提升为主后,该锁 key 不存在,客户端 B 可以成功获取锁——此时 A 和 B 同时持有锁,互斥性被打破。这就是 RedLock 算法(Redis 官方提出)试图解决的问题,但其本身也存在争议。选项 B 错误,从节点支持所有写命令(通过主节点转发)。选项 C 描述的是主从异步复制的延迟现象,但不会导致 setIfAbsent 行为异常。",
"source": null,
"related": []
},
{
"id": "sc-007",
"type": "single_choice",
"difficulty": 4,
"tags": [
"distributed-lock",
"lock-design"
],
"question": "generateLockValue 方法返回的是 UUID + threadId 组合。为什么不直接用 userId 作为 lockValue?",
"options": {
"A": "userId 是数字,不能作为 Redis 的 value",
"B": "如果用 userId 作为 lockValue,持有锁的线程无法唯一标识,unlock 时的 Lua 脚本无法区分不同请求来源",
"C": "UUID 生成速度比 userId 快",
"D": "userId 不满足 Redis key 的命名规范"
},
"answer": "B",
"explanation": "unlock 的 Lua 脚本通过比较锁的 value 来判断是否为当前持有者的锁。如果用 userId 作为 lockValue,那么同一用户的多次请求(可能在不同线程甚至不同 JVM 中)会产生相同的 lockValue,导致:(1) 后来的请求的 SETNX 会失败(锁已被自己的旧请求持有);(2) 或者在锁过期后,新请求获取锁后用 userId 解锁时,可能误删已过期但被其他实例获取的锁。UUID + threadId 能保证每个锁持有者有唯一标识,确保 unlock 时精确匹配。选项 A 错误,Redis value 可以是任何字符串。选项 D 错误,lockKey 用 userId,lockValue 是锁的凭证。",
"source": null,
"related": []
},
{
"id": "sc-008",
"type": "single_choice",
"difficulty": 5,
"tags": [
"distributed-lock",
"lock-design"
],
"question": "RedisLockUtil 的 tryLock 方法在获取锁失败时会抛出异常吗?如果 acquireTimeout 内未获取到锁,executeWithLock 会如何表现?",
"options": {
"A": "tryLock 返回 false,executeWithLock 抛出 RuntimeException",
"B": "tryLock 返回 false,executeWithLock 返回 null",
"C": "tryLock 抛出 InterruptedException,executeWithLock 捕获并重试",
"D": "tryLock 会无限等待直到获取锁"
},
"answer": "A",
"explanation": "tryLock 方法在 acquireTimeout(默认 3000ms)内未获取到锁时返回 false(而非抛异常)。executeWithLock 中,locked 变量为 false 时会主动抛出 RuntimeException(\"获取分布式锁失败\")。这意味着获取锁超时被视为错误场景,需要调用方处理该异常。在 doThumb 场景中,用户点赞操作获取锁失败会直接报错,而不是静默返回 false——这是合理的降级策略,避免在高并发下静默失败。",
"source": null,
"related": []
}
]
}