JDK 21新特性---虚拟线程
虚拟线程是什么
- 虚拟线程是与原来的平台线程类似的线程,它也是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
阻塞操作。