bigywater.tech
Java · 290

SubmissionPublisher:异步发布如何处理慢订阅者

SubmissionPublisher 是 JDK 自带的 Flow 发布者。缓冲区到底多大?订阅者慢了 submit 会怎样?offer 与 submit 的差别?这篇逐项触发,还撞到了一个会让监控调用卡住的细节。

3 → 4,100 → 128
maxBufferCapacity 实际取整后的容量
-1
缓冲满时 offer 在回调返回 false 后的返回值(丢弃一个)
ForkJoinPool.commonPool
默认执行器,订阅者回调所在线程

缓冲区与阻塞

实测 · 本机执行输出
    默认容量 Flow.defaultBufferSize()=256;getMaxBufferCapacity=256
      maxBufferCapacity=1   getMaxBufferCapacity()=1    订阅者始终没有 request,submit 成功 1 个后阻塞(线程状态 WAITING)
      maxBufferCapacity=3   getMaxBufferCapacity()=4    订阅者始终没有 request,submit 成功 4 个后阻塞(线程状态 WAITING)
      maxBufferCapacity=4   getMaxBufferCapacity()=4    订阅者始终没有 request,submit 成功 4 个后阻塞(线程状态 WAITING)
      maxBufferCapacity=5   getMaxBufferCapacity()=8    订阅者始终没有 request,submit 成功 8 个后阻塞(线程状态 WAITING)
      maxBufferCapacity=8   getMaxBufferCapacity()=8    订阅者始终没有 request,submit 成功 8 个后阻塞(线程状态 WAITING)
        另一个线程在 submit 阻塞期间调用 estimateMaximumLag():500 ms 后线程状态 = WAITING(仍被阻塞)
        同时调用 getNumberOfSubscribers():仍被阻塞
      maxBufferCapacity=10  getMaxBufferCapacity()=16   订阅者始终没有 request,submit 成功 16 个后阻塞(线程状态 WAITING)
      maxBufferCapacity=100 getMaxBufferCapacity()=128  订阅者始终没有 request,submit 成功 128 个后阻塞(线程状态 WAITING)

offer 与丢弃

实测 · 本机执行输出
        onDrop 回调:元素 5,返回 false 表示不重试
        onDrop 回调:元素 6,返回 false 表示不重试
        onDrop 回调:元素 7,返回 false 表示不重试
        onDrop 回调:元素 8,返回 false 表示不重试
      offer(1..8) 的返回值(正数=估计滞后,负数=丢弃数的相反数):1 2 3 4 -1 -1 -1 -1
      订阅者数 1;hasSubscribers=true;estimateMaximumLag=4;estimateMinimumDemand=-4
        onNext 1
        onNext 2
        onNext 3
        onNext 4
      request(10) 后 estimateMaximumLag=0
        onNext 9
      带超时的 offer(9):1
        onComplete

线程、顺序与异常

实测 · 本机执行输出
    默认执行器的线程名(两个订阅者,各 request 无界):
      [ForkJoinPool.commonPool-worker-1] S1 onSubscribe
      [ForkJoinPool.commonPool-worker-2] S2 onSubscribe
      [ForkJoinPool.commonPool-worker-1] S1 onNext 1
      [ForkJoinPool.commonPool-worker-2] S2 onNext 1
      [ForkJoinPool.commonPool-worker-1] S1 onNext 2
      [ForkJoinPool.commonPool-worker-2] S2 onNext 2
      [ForkJoinPool.commonPool-worker-1] S1 onComplete
      [ForkJoinPool.commonPool-worker-2] S2 onComplete
    同一个订阅者收到元素的顺序(submit 1..6 后,订阅者是否按顺序):
      [1, 2, 3, 4, 5, 6]
    订阅者的 onNext 抛异常:
      [ForkJoinPool.commonPool-worker-2] onNext 1
      [ForkJoinPool.commonPool-worker-2] onNext 2
      [ForkJoinPool.commonPool-worker-2] onError java.lang.IllegalStateException: 订阅者处理 2 时失败
      之后订阅者数 = 0(出错的订阅者已被移除)
    request(0) 与 request(-1):
      [ForkJoinPool.commonPool-worker-2] request(0) → onError java.lang.IllegalArgumentException: non-positive subscription request
      [ForkJoinPool.commonPool-worker-2] request(-1) → onError java.lang.IllegalArgumentException: non-positive subscription request
    closeExceptionally 与 close 之后 submit:
      [ForkJoinPool.commonPool-worker-2] onError java.lang.RuntimeException: 发布者主动失败
      isClosed=true;getClosedException=java.lang.RuntimeException: 发布者主动失败
      关闭后 submit:java.lang.IllegalStateException: Closed

分步演示

SubmissionPublisher
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:自定义 Executor 下的线程行为、多个订阅者慢速时的互相影响细节、estimateMaximumLag 阻塞的完整机制(只用 jstack 看到停在锁上)、性能吞吐、JDK 其它版本
    京ICP备2026067502号-1京公网安备11010502063487号