park 与 unpark 是 AQS、ReentrantLock、CompletableFuture 等所有并发工具的底层。它们的许可模型和 wait/notify 有什么不同?遇到中断怎么办?这篇逐项真实运行,并用它们写一个简单的互斥锁。
先 unpark(自己)再 park() → 立刻返回(耗时 8 μs):许可被保存了 连续 unpark 三次再 park 三次:成功返回了 1 次,第 2 次阻塞(线程状态 WAITING);许可最多保存一个,不累加 对照 Object.wait/notify:先 notify 再 wait → notify 丢失,wait 一直等到 300 ms 超时才返回(耗时 306 ms) park() 阻塞时线程状态 = WAITING parkNanos(5 s) 阻塞时线程状态 = TIMED_WAITING LockSupport.getBlocker(t4) = 我是 blocker 对象
unpark → 对方 park 返回的延迟(2000 次):中位 1.9 μs,P90 2.0 μs,P99 11.0 μs,最大 120.8 μs parkNanos(1 ms) 实际耗时(20 次):中位 1.51 ms,最小 1.50 ms,最大 1.51 ms parkNanos(50 μs) 实际耗时(20 次):中位 79 μs,最小 77 μs,最大 82 μs parkUntil(现在 + 120 ms) 实际耗时 127 ms parkNanos(-5) 与 parkNanos(0) 立刻返回:耗时 0 μs
线程已被中断时 park():立刻返回(耗时 15 μs),中断标志仍是 true(park 不清除、也不抛出) 连续再 park 两次也是立刻返回(累计 1 μs),中断标志仍是 true 错误写法 while (!cond) park(),线程带着中断标志进入循环:200 ms 内空转了 3239916 次(后 100 ms 就有 1670499 次),相当于忙等,占满一个核 正确写法:park 返回后检查并清除中断标志(Thread.interrupted()),记下来,退出循环后再恢复:200 ms 内只循环了 1 次(之后正常阻塞),中断状态没有丢(退出时 sawInt = true)
2 个线程各加锁 20000 次的计数器:park/unpark 写的 FIFO 互斥锁 计数 = 40000(期望 40000),耗时 97 ms;公平 ReentrantLock 计数 = 40000,耗时 17 ms 8 个线程各加锁 20000 次的计数器:park/unpark 写的 FIFO 互斥锁 计数 = 160000(期望 160000),耗时 401 ms;公平 ReentrantLock 计数 = 160000,耗时 383 ms