JAVA 21 - VirtualThread 기능 살펴보기
By on January 2, 2024
Virtual Thread (Java 21)
Java 21에 추가된 Virtual Thread 는 처리량이 높은 동시 어플리케이션을 작성 할 수 있는 경량 스레드 이다.
기존에 사용 하던 Thread 는 OS Thread 를 랩핑 해서 사용 하는 형태로 OS의 총 스레드 개수에 따라 최대 Thread 의 생성 갯수가 정해지며 생성 비용이 높다.
신규로 추가된 Virtual Thread 도 기존의 Thread를 상속 받아 구현된 클래스 지만 특정한 OS Thread를 1:1로 사용 하는 방식이 아닌 OS Thread 를 Pool 방식으로 사용 하는 형태 라고 보면 된다.
단. Synchronized 를 호출하게 되면 특정한 OS Thread를 점유하여 사용 하므로 유의 해야한다.
Thread | Virtual | Thread |
---|---|---|
Stack 사이즈 | ~2MB | ~10KB |
생성시간 | ~1ms | ~1µs |
컨텍스트 스위칭 | ~100µs | ~10µs |
기존의 Springboot webFlux 를 사용 하면 reactive 기반의 개발이 가능 하였으나 webFlux 를 활용하기 위해서는 많은 공부가 필요했다..
하지만 자바의 Virtual Thread 를 사용 하면 기존 방식과 달라지는 부분이 많이 없기 때문에 손쉽게 활용 할 수 있다.
VirtualThread 생성 및 SpringBoot 설정방법
public void createVirtualThread(){
// Virtual Thread 생성 방법 1
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();
// Virtual Thread 생성 방법 2
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();
// Virtual Thread 생성 방법 3
Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);
Runnable task = () -> {
System.out.println("Thread ID: " + Thread.currentThread().threadId());
};
// name "worker-0"
Thread t1 = builder.start(task);
t1.join();
System.out.println(t1.getName() + " terminated");
// name "worker-1"
Thread t2 = builder.start(task);
t2.join();
System.out.println(t2.getName() + " terminated");
// Virtual Thread 생성 방법 4(ExecutorService 활용)
try(ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()){
Future<?> future = myExecutor.submit(() -> System.out.println("Running thread"));
future.get();
System.out.println("Task completed");
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
SpringBoot 3.1 버전대를 사용하는 경우 아래 빈 등록을 통해서 Virtual Thread 를 사용 할 수 있다.
@Bean
public TomcatProtocolHandlerCustomizer<?> tomcatProtocolHandlerCustomizer(){
return protocolHandler -> {
protocolHandler.setExecutors(Executors.newVirtualThreadPerTaskExecutor());
};
}
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor(){
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
SpringBoot 3.2 버전 부터는 application.yaml 에서 설정이 가능 하다.
# application.yaml
spring:
threads:
virtual:
enabled: true
MultiThread Server - Client
- EchoServer
public class EchoServer {
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(9090)) {
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 9090 or listening for a connection");
System.out.println(e.getMessage());
}
}
}
- EchoClient
public class EchoClient {
public static void main(String[] args) throws IOException {
try (
Socket echoSocket = new Socket("localhost", 9090);
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 localhost");
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to localhost ");
System.exit(1);
}
}
}