JDK 21新特性---虚拟线程

发布时间 2023-09-28 16:09:28作者: 大胖头

虚拟线程是什么

  • 虚拟线程是与原来的平台线程类似的线程,它也是Java.Lang.Thread的一个实例,但它是由Jvm进行管理和调度的。
  • 与虚拟内存的实现方式类似,在Jvm中会存在一个Map来维护虚拟线程与实际系统线程的对应关系。
  • 当虚拟线程运行时,Jvm会把它分配到一个平台线程上,这个平台线程被称为Carrier。当虚拟线程遇到I/O阻塞被挂起后,这个Carrier就空闲下来,Jvm会分配其他的虚拟线程到这个Carrier上。
  • 根据虚拟线程的特性来说,它适合执行一些耗时的I/O阻塞式的任务。

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

  1. 管理与调度不同:平台线程是由OS进行管理和调度的,而虚拟线程则是由JVM进行管理与调度的。
  2. 线程规模不同:平台线程的规模受到OS的限制,而虚拟线程则没有这个限制,理论上来说虚拟线程的最大数量要比平台线程大得多。
  3. 使用成本不同:由于虚拟内存是受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.
    

使用虚拟线程的注意事项

  1. 直接使用虚拟线程,而不要像使用平台线程那样进行池化。
  2. 虽然没有了OS的限制,可以创造出大量的虚拟线程,但是要注意,对于有限资源的访问(如数据库)等还是要加以限制。
  3. 避免使用synchronized阻塞操作。