Vert.x executeBlocking两种执行行为差异原因咨询
理解Vert.x executeBlocking与setPeriodic的行为差异
这是个非常典型的Vert.x异步编程陷阱,我来帮你拆解两种实验的本质区别:
核心原因:Event Loop上下文串行化 + 变量捕获问题
第一组实验的问题根源
你用vertx.setPeriodic每秒触发的任务,全程在同一个Event Loop线程里执行。而默认调用executeBlocking时,ordered参数为true——Vert.x会把所有来自这个Event Loop上下文的阻塞任务放到一个串行队列中,前一个阻塞任务没执行完,后面的任务只能排队等待。
再加上你的blockingMethod要耗时2秒,触发频率却是每秒1次,就会出现连锁问题:
- 第1秒:counter递增为1,提交阻塞任务A到队列并开始执行
- 第2秒:counter递增为2,提交阻塞任务B到队列排队
- 第3秒:counter递增为3,提交阻塞任务C到队列排队
- 第3秒末:任务A执行完成,若你的代码里直接引用全局counter变量输出结果,此时拿到的已经是3(而非触发时的1),你会误以为任务A的结果丢失了
- 后续任务执行时,同样会拿到最新的counter值,最终看起来就像“每2次触发仅输出1次结果”
本质上任务并没有丢失,只是串行执行导致任务执行时的变量值被后续Event Loop的修改覆盖,加上任务堆积延迟,让你产生了“事件丢失”的错觉。
第二组实验为什么正常
你把阻塞逻辑封装到AsyncClass的result方法里,通过wrapperMethod触发executeBlocking,核心变化在于:
- 每次触发时,你应该是把触发瞬间的counter值封装到了AsyncClass实例中(比如通过构造参数传递),而非引用全局共享的counter变量
- 或者
wrapperMethod在调用executeBlocking时,用局部变量捕获了触发时的counter值,再传递给阻塞逻辑
这样每个阻塞任务持有的都是独立的、触发时的变量快照,不会被后续Event Loop的修改覆盖。哪怕还是用默认的ordered=true串行执行,每个任务的结果也能和触发顺序一一对应,自然不会出现“丢事件”的情况。
代码示例对比
错误的第一组写法(导致变量覆盖)
AtomicInteger counter = new AtomicInteger(0); vertx.setPeriodic(1000, id -> { counter.incrementAndGet(); vertx.executeBlocking(future -> { blockingMethod(); future.complete(counter.get()); // 执行时拿到的是最新counter值,而非触发时的 }, result -> { System.out.println("结果:" + result.result()); }); });
正确的第二组写法(保存触发时的变量快照)
AtomicInteger counter = new AtomicInteger(0); vertx.setPeriodic(1000, id -> { int currentCount = counter.incrementAndGet(); AsyncClass asyncTask = new AsyncClass(currentCount); // 传递触发时的count值 asyncTask.wrapperMethod(vertx, result -> { System.out.println("结果:" + result.result()); }); }); class AsyncClass { private final int taskCount; public AsyncClass(int taskCount) { this.taskCount = taskCount; } public void wrapperMethod(Vertx vertx, Handler<AsyncResult<Integer>> resultHandler) { vertx.executeBlocking(future -> { blockingMethod(); future.complete(taskCount); // 使用实例持有的触发时的count值 }, resultHandler); } }
额外建议
- 当用
executeBlocking处理Event Loop触发的周期性任务时,一定要捕获触发时的变量快照,避免共享变量被后续修改覆盖 - 如果不需要任务串行执行,可以把
executeBlocking的ordered参数设为false,让任务并行提交到Worker线程池,提升吞吐量(注意保证阻塞逻辑的线程安全性) - 可通过
vertx.createSharedWorkerExecutor创建自定义大小的Worker线程池,避免默认池被占满影响其他业务任务
内容的提问来源于stack exchange,提问作者user5685250
相关产品推荐
相关产品推荐

