Java21의 Virtual Thread (2)

나민혁·2024년 10월 25일

들어가며

앞선 포스팅에서는 가상 스레드의 탄생 배경, 목적, 원리 등을 알아봤으니 실제로 자바에서 어떻게 동작하는지 알아보자. 정말 간단하게 확인해볼 예정이다.

플랫폼 스레드 사용방법

public class ThreadRunner {
    public static void main(String[] args) {
        threadRun();
    }

    public static void threadRun() {
        new Thread(() -> {
            System.out.println("start 플랫폼 스레드입니다.");
        }).start();

        Runnable runnable = () -> {
            System.out.println("runnable 플랫폼 스레드입니다.");
        };

        Thread platformThread = new Thread(runnable);
        platformThread.start();

        Thread platformThread2 = new Thread(runnable);
        platformThread2.setName("JVM-Thread");
        platformThread2.start();

        System.out.println("플랫폼 스레드인지 확인 " + !platformThread.isVirtual());

        try (ExecutorService executorService = Executors.newFixedThreadPool(3)) {
            for (int i = 0; i < 3; i++) {
                executorService.submit(runnable);
            }
        }
    }
}

일반적인 스레드 사용 방법이다. runnable을 생성해서 start 해주거나, Thread를 만들어서 start 해주면 되었다.

Java 21에서 가상 스레드가 등장하면서 나온 isVirtual 메서드를 통해서 플랫폼 스레드인지 확인해보았다.

실제 실행결과도 예상하는 대로이다.

위 실행 결과가 플랫폼 스레드인지 확인 true로 나오는걸 확인 할 수 있을 것이다.

그렇다면 가상 스레드 사용방법은 어떨까?

가상 스레드 사용방법

이전 글에서 가상 스레드의 지향점 중 이러한 내용이 있었다.

기존의 java.lang.Thread API를 사용하는 코드가 최소한의 변경으로 가상 스레드를 적용할 수 있게 한다.

실제로 가상 스레드를 사용하는 방법은 크게 다르지 않다.

public class ThreadRunner {
    public static void main(String[] args) {
        threadRun();
    }

    public static void run() {
        Thread.startVirtualThread(() -> {
            System.out.println("start 가상 스레드입니다.");
        });

        Runnable runnable = () -> {
            System.out.println("runnable 가상 스레드입니다.");
        };

        Thread virtualThread = Thread.ofVirtual().start(runnable);

        Thread.Builder builder = Thread.ofVirtual().name("JVM-Thread");
        Thread virtualThread2 = builder.start(runnable);

        System.out.println("버츄얼 스레드인지 확인 " + virtualThread.isVirtual());

        try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 3; i++) {
                executorService.submit(runnable);
            }
        }
    }
}

start 대신 startVirtualThread 일반적인 Thread를 형변환(?) 이라고 표현하는게 맞을려나 ofVirtual() 을 이용해서 가상스레드로 선언해줄 수 있다.

실제 실행 결과역시 isVirtual() 을 통해서 가상 스레드인지 확인해보면 true 라고 반환이 될 것이다.

실제로 가상 스레드임을 확인 할 수 있었다. 실제로 이렇게 Java에서는 가상 스레드를 사용할 때 기본적인 플랫폼 스레드와의 차이가 크게 없다.
이게 가상 스레드를 만든 Project Loom의 지향점 이었기 때문이라고 생각한다.

가상 스레드 vs 플랫폼 스레드 실행 시간 비교

가상 스레드

Executors 를 사용하여 10만개의 가상 스레드를 만들고 2초간 대기하도록 한뒤에 전체 실행시간을 측정해보려고 한다.
Blocking 이 발생하면 다른 가상 스레드가 실행되기 때문에 제대로 동작한다면 약 2초를 조금 넘기는 시간 안에 완료되어야 한다고 한다.

실제로 그런지 확인해보자.

public class ThreadRunner {
    public static void main(String[] args) {
        run();
    }

    public static void virtualThreadRun() {
        while (true) {
            long start = System.currentTimeMillis();
            try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
                for (int i = 0; i < 100_000; i++) {
                    executor.submit(() -> {
                        Thread.sleep(Duration.ofSeconds(2));
                        return null;
                    });
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("가상 스레드 실행 시간: " + (end - start) + "ms");
        }
    }
}

조금의 오차는 있지만 2초에서 크게 벗어나지 않는 것을 알 수 있었다.

그러면 플랫폼 스레드는 어떤지 확인해보자

플랫폼 스레드

플랫폼 스레드는 Blocking이 발생하기 때문에 ThreadPool과 동일하지 않다면 2초가 나오지 않을 것이다. 예를 들어 ThreadPool이 100개이고 플랫폼 스레드를 100개를 실행시킨다면 2초근처가 나오겠지만 그렇지 않으면 Blocking으로 인해 (플랫폼 스레드의 갯수 / ThreadPool * 2초) 가 나올 것이다.

한번 확인해보자 스레드풀을 100개로 할 것이기 때문에 10만개를 하면 하루종일 기다려야하니 1000개만 해보았다.

public class ThreadRunner {
    public static void main(String[] args) {
        threadRun();
    }

    public static void platformThreadRun() {
        while (true) {
            long start = System.currentTimeMillis();
            try (var executor = Executors.newFixedThreadPool(100)) {
                for (int i = 0; i < 100_0; i++) {
                    executor.submit(() -> {
                        Thread.sleep(Duration.ofSeconds(2));
                        return null;
                    });
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("플랫폼 스레드 실행 시간: " + (end - start) + "ms");
        }
    }
}

실제로 동작한지 1분 12초가 지났는데 플랫폼 스레드가 전부 동작하는데 20초가 걸렸기 때문에 3개밖에 찍히지 않은 것을 확인 할 수 있다.

이것을 통해서 Blocking이 존재 할 때 가상 스레드가 얼마나 뛰어난 효과를 가져다 줄 수 있는지 확인 할 수 있었다.

결론

단순하게 이렇게 보자면 가상 스레드가 무조건적으로 좋아보인다. 일반 스레드와 사용법에서 큰 차이가 없고, I/O Blocking이 발생하는 경우에 가상 스레드가 좋은 성능을 보인다.

자바에서 가볍게 사용법과 실제로 성능을 확인해보았다. 다음 글에서는 스프링부트에서도 단순하게 쓸 수 있는지 알아보려고 한다.

0개의 댓글