JDK 21新特性---虚拟线程(VirtualThread)

虚拟线程是什么

虚拟线程是与原来的平台线程类似的线程,它也是Java.Lang.Thread的一个实例,但它是由Jvm进行管理和调度的。

与虚拟内存的实现方式类似,在Jvm中会存在一个Map来维护虚拟线程与实际系统线程的对应关系。

当虚拟线程运行时,Jvm会把它分配到一个平台线程上,这个平台线程被称为Carrier。当虚拟线程遇到I/O阻塞被挂起后,这个Carrier就空闲下来,Jvm会分配其他的虚拟线程到这个Carrier上。

根据虚拟线程的特性来说,它适合执行一些耗时的I/O阻塞式的任务。

虚拟线程与平台线程的区别是什么

  • 管理与调度不同:平台线程是由OS进行管理和调度的,而虚拟线程则是由JVM进行管理与调度的。
  • 线程规模不同:平台线程的规模受到OS的限制,而虚拟线程则没有这个限制,理论上来说虚拟线程的最大数量要比平台线程大得多。
  • 使用成本不同:由于虚拟内存是受JVM管理的,因此它的分配不需要进行系统调用,也不受系统上下文切换的影响。

    什么场景下使用虚拟线程

    虚拟线程适合在高并发场景下,执行可能带来长时间I/O阻塞的任务。官方给出的示例是一个Server-Client模式的例子。

    public class EchoServer {
    
     public static void main(String[] args) throws IOException {
    
         if (args.length != 1) {
             System.err.println("Usage: java EchoServer <port>");
             System.exit(1);
         }
    
         int portNumber = Integer.parseInt(args[0]);
         try (
             ServerSocket serverSocket =
                 new ServerSocket(Integer.parseInt(args[0]));
         ) {                
             while (true) {
                 Socket clientSocket = serverSocket.accept();
                 // Accept incoming connections
                 // Start a service thread
                 Thread.ofVirtual().start(() -> {
                     try (
                         PrintWriter out =
                             new PrintWriter(clientSocket.getOutputStream(), true);
                         BufferedReader in = new BufferedReader(
                             new InputStreamReader(clientSocket.getInputStream()));
                     ) {
                         String inputLine;
                         while ((inputLine = in.readLine()) != null) {
                             System.out.println(inputLine);
                             out.println(inputLine);
                         }
    
                     } catch (IOException e) { 
                         e.printStackTrace();
                     }
                 });
             }
         } catch (IOException e) {
             System.out.println("Exception caught when trying to listen on port "
                 + portNumber + " or listening for a connection");
             System.out.println(e.getMessage());
         }
     }
    }
    
    public class EchoClient {
     public static void main(String[] args) throws IOException {
         if (args.length != 2) {
             System.err.println(
                 "Usage: java EchoClient <hostname> <port>");
             System.exit(1);
         }
         String hostName = args[0];
         int portNumber = Integer.parseInt(args[1]);
         try (
             Socket echoSocket = new Socket(hostName, portNumber);
             PrintWriter out =
                 new PrintWriter(echoSocket.getOutputStream(), true);
             BufferedReader in =
                 new BufferedReader(
                     new InputStreamReader(echoSocket.getInputStream()));
         ) {
             BufferedReader stdIn =
                 new BufferedReader(
                     new InputStreamReader(System.in));
             String userInput;
             while ((userInput = stdIn.readLine()) != null) {
                 out.println(userInput);
                 System.out.println("echo: " + in.readLine());
                 if (userInput.equals("bye")) break;
             }
         } catch (UnknownHostException e) {
             System.err.println("Don't know about host " + hostName);
             System.exit(1);
         } catch (IOException e) {
             System.err.println("Couldn't get I/O for the connection to " +
                 hostName);
             System.exit(1);
         } 
     }
    }

    如何使用虚拟线程

    官方提供了多种使用虚拟线程的方式:

Thread.ofVirtual()

     Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
     thread.join();

Thread.Builder()

      try {
                  Thread.Builder builder = Thread.ofVirtual().name("MyThread");
                  Runnable task = () -> {
                      System.out.println("Running thread");
                  };
                  Thread t = builder.start(task);
                  System.out.println("Thread t name: " + t.getName());
                  t.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }

Executors.newVirtualThreadPerTaskExecutor()

      try (ExecutorService myExecutor =
                  Executors.newVirtualThreadPerTaskExecutor()) {
                  Future<?> future =
                      myExecutor.submit(() -> System.out.println("Running thread"));
                  future.get();
                  System.out.println("Task completed");
              } catch (InterruptedException | ExecutionException e) {
                  e.printStackTrace();
              }

虚拟线程 VS 平台线程

线程执行100个sleep 1秒的任务

      var vs = Executors.newFixedThreadPool(200);
      List<Future<Integer>> futures = new ArrayList<>();
      var begin = System.currentTimeMillis();
      for (int i = 0; i < 1000; i++) {
    var future = vs.submit(() -> {
        Thread.sleep(1000L);
        return a.addAndGet(1);
    });
    futures.add(future);
  }

耗时结果对比

      Platform Thread Exec time: 5050ms.
      Virtual Thread Exec time: 1039ms.

使用虚拟线程的注意事项

  • 直接使用虚拟线程,而不要像使用平台线程那样进行池化。
  • 虽然没有了OS的限制,可以创造出大量的虚拟线程,但是要注意,对于有限资源的访问(如数据库)等还是要加以限制。
  • 避免使用synchronized阻塞操作。
除非注明,否则均为哦豁原创文章,转载必须以链接形式标明本文链接
guest

0 评论
最多投票
最新 最旧
内联反馈
查看所有评论