bigywater.tech
Java · 231

LockSupport:park 与 unpark 的许可模型

park 与 unpark 是 AQS、ReentrantLock、CompletableFuture 等所有并发工具的底层。它们的许可模型和 wait/notify 有什么不同?遇到中断怎么办?这篇逐项真实运行,并用它们写一个简单的互斥锁。

1 个
每个线程最多保存的许可数(不累加)
1.9 μs
unpark 到对方 park 返回的延迟中位数
3239916 次
带中断标志循环 park 时 200 ms 内的空转次数

许可模型

实测 · 本机执行输出
  先 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)

写一个 FIFO 互斥锁

实测 · 本机执行输出
  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

分步演示

LockSupport
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS 10 核
    • 没有验证:Linux 上 futex 实现的延迟与精度(这里的取整可能是 macOS 的特点)、虚拟线程的 park(见虚拟线程相关的篇目)、伪唤醒是否会发生(这篇没有测)、自写互斥锁在 2 线程时较慢的原因
    京ICP备2026067502号-1京公网安备11010502063487号