Vertx 使用 虚拟线程

Vertx 是支持虚拟线程,并且很轻松的就能切换到虚拟线程,还能让异步代码用同步的写法

1. 部署虚拟线程 verticle

通过给部署设置进行setThreadingModel 将线程模型设置为虚拟线程即可

Vertx vertx = Vertx.vertx();
AbstractVerticle verticle = new AbstractVerticle() {
   @Override
   public void start() {
       HttpClient client = vertx.createHttpClient();
       HttpClientRequest req = Future.await(client.request(
               HttpMethod.GET,
               8080,
               "localhost",
               "/"));
       HttpClientResponse resp = Future.await(req.send());
       int status = resp.statusCode();
       Buffer body = Future.await(resp.body());
   }
};

// 使用虚拟线程
vertx.deployVerticle(verticle, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD));

2. 使用虚拟线程等待 Future

Future.await 使用虚拟线程等待 Future 完成。虚拟线程 verticle 能够等待 futures 并同步获取结果。这意味着他能将异步代码通过同步的写法来实现。

注意: Future.await 需要在虚拟线程 verticle 或者 虚拟线程环境下才能使用

// 读取文件, 并返回 Future
Future<Buffer> bufferFuture = vertx.fileSystem().readFile("./settings.gradle.kts");
// 读取文件,并等待结果
Buffer awaitBuffer = Future.await(vertx.fileSystem().readFile("./settings.gradle.kts"));
// JDK
Buffer body = Future.await(Future.fromCompletionStage(completionStage));

在官网中有一句话

You can use Future.await to suspend the current virtual thread until the awaited result is available.
您可以使用Future.await暂停当前虚拟线程,直到等待的结果可用。
The virtual thread is effectively blocked, but the application can still process events.
虚拟线程随便阻塞,他不会影响到事件循环线程的

另外需要注意并发带来的BUG

await 可能会导致线程切换,在切换期间其他线程可能会修改某些字段,因此在 await 之前读取的值,在 await 之后可能已经过时。

int value = counter;  // 读取 counter 值
value += Future.await(getRemoteValue());  // 等待异步任务完成,并修改 value
counter = value;  // 将 value 赋值回 counter

问题分析

  1. Future.await(getRemoteValue()) 可能会导致 线程挂起并恢复(可能换了个线程继续执行)。
  2. 在 await 期间,其他线程可能修改了 counter 的值,导致 value 变得不再是最新的。
  3. 这样最终赋值 counter = value; 时,可能会覆盖掉其他线程的更新。

正确写法为

counter += Future.await(getRemoteValue());  // 直接在 await 之前读写字段

3. 等待多个Future

Future<String> f1 = getRemoteString();
Future<Integer> f2 = getRemoteValue();
CompositeFuture res = Future.await(Future.all(f1, f2));
String v1 = res.resultAt(0);
Integer v2 = res.resultAt(1);

4. 避免阻塞 verticle

ReentrantLock#lock() 直接阻塞住当前 verticle,导致 在这 3 秒内不会处理其他事件。
由于 Vert.x 不能感知这种阻塞,可能会影响整个事件调度,造成系统吞吐量下降。

class BlockingVerticle : AbstractVerticle() {
    override fun start() {
        vertx.setPeriodic(1000) {
            println("Start blocking operation")
            val lock = ReentrantLock()
            lock.lock()  // 这里会阻塞当前 verticle
            try {
                Thread.sleep(3000) // 模拟长时间阻塞
                println("End blocking operation")
            } finally {
                lock.unlock()
            }
        }
    }
}

官方建议: 如果 verticle 可能会被阻塞,应该使用多个实例进行部署,类似 worker verticle 来保证并发性。

val vertx = Vertx.vertx()
val options = DeploymentOptions().setInstances(4) // 部署 4 个实例
vertx.deployVerticle(BlockingVerticle::class.java.name, options)

5. ThreadLocal

线程局部变量仅在执行上下文任务时是可靠的。

ThreadLocal<String> local = new ThreadLocal();
local.set(userId);
HttpClientRequest req = Future.await(client.request(HttpMethod.GET, 8080, "localhost", "/"));
HttpClientResponse resp = Future.await(req.send());

你可能感兴趣的:(开发语言,java)