Java SE 19 虚拟线程


说明#
虚拟线程(Virtual Threads)是在Project Loom中开发的,并从 Java SE 19 开始作为预览功能引入 JDK。

在线程模型下,一个 Java 线程相当于一个操作系统线程,而这些线程是很消耗资源的,如果启动的线程过多,会给整个系统的稳定性带来风险。

虚拟线程解决了这个问题,从 Java 代码的角度来看,虚拟线程感觉就像普通的线程,但它们不是 1:1 地映射到操作系统线程上。

有一个所谓的载体线程池,一个虚拟线程被临时映射到该池中。一旦虚拟线程遇到阻塞操作,该虚拟线程就会从载体线程中移除,而载体线程可以执行另一个虚拟线程(新的或之前被阻塞的)。

所以阻塞的操作不再阻塞执行的线程。这使得我们可以用一个小的载体线程池来并行处理大量的请求。

示例#
场景

启动 1000 个任务,每个任务等待一秒钟(模拟访问外部API),然后返回一个结果(在这个例子中是一个随机数)。

任务类如下

package git.snippets.vt;

import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author <a href="mailto:410486047@qq.com">Grey</a>
 * @date 2022/9/21
 * @since 19
 */
public class Task implements Callable<Integer> {

    private final int number;

    public Task(int number) {
        this.number = number;
    }

    @Override
    public Integer call() {
        System.out.printf("Thread %s – Task %d waiting…%n", Thread.currentThread().getName(), number);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.printf("Thread %s – Task %d canceled.%n", Thread.currentThread().getName(), number);
            return -1;
        }
        System.out.printf("Thread %s – Task %d finished.%n", Thread.currentThread().getName(), number);
        return ThreadLocalRandom.current().nextInt(100);
    }
}
接下来,我们测试使用线程池开启 100 个线程处理 1000 个任务需要多长时间。

package git.snippets.vt;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @author <a href="mailto:410486047@qq.com">Grey</a>
 * @date 2022/9/21
 * @since 19
 */
public class App {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(100);
        List<Task> tasks = new ArrayList<>();
        for (int i = 0; i < 1_000; i++) {
            tasks.add(new Task(i));
        }
        long time = System.currentTimeMillis();
        List<Future<Integer>> futures = executor.invokeAll(tasks);
        long sum = 0;
        for (Future<Integer> future : futures) {
            sum += future.get();
        }
        time = System.currentTimeMillis() – time;
        System.out.println("sum = " + sum + "; time = " + time + " ms");
        executor.shutdown();
    }
}

运行结果如下

Thread pool-1-thread-1 – Task 0 waiting…
Thread pool-1-thread-3 – Task 2 waiting…
Thread pool-1-thread-2 – Task 1 waiting…
……
sum = 49879; time = 10142 ms
接下来,我们用虚拟线程测试整个事情。因此,我们只需要替换这一行

ExecutorService executor = Executors.newFixedThreadPool(100);
替换为

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
执行效果如下

Thread  – Task 0 waiting…
Thread  – Task 2 waiting…
Thread  – Task 3 waiting…
……
sum = 48348; time = 1125 ms
1125 ms VS 10142 ms,性能提升非常明显。

注:本示例需要在 JDK 19 下运行,且需要增加 –enable-preview 参数,在 IDEA 下,这个参数配置

我们已经了解了创建虚拟线程的一种方法:使用Executors.newVirtualThreadPerTaskExecutor()创建的执行器服务,为每个任务创建一个新的虚拟线程。

使用Thread.startVirtualThread()或Thread.ofVirtual().start(),我们也可以明确地启动虚拟线程。

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/294447.html

(0)
上一篇 2022年12月2日
下一篇 2022年12月2日

相关推荐

发表回复

登录后才能评论