프로그래밍/Java

Java Virtual Thread (3), executorService 생성

개발정리 2024. 6. 22. 21:41

 Executor Service

플랫폼 스레드에서 executorService를 실행해보자. 

@Slf4j
public class VirtualThreadExecutorCreation {
    private static final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            log.info("1) run. thread: {}", Thread.currentThread());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("2) run. thread: {}", Thread.currentThread());
        }
    };

    public static void main(String[] args) {
        log.info("1) main thread: " + Thread.currentThread());

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executorService.submit(runnable);
        }
        executorService.close();
        log.info("2) main thread: " + Thread.currentThread());
    }
}
20:53:53.035 [main] INFO org.example.VirtualThreadExecutorCreation -- 1) main thread: Thread[#1,main,5,main]
20:53:53.036 [pool-1-thread-1] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#20,pool-1-thread-1,5,main]
20:53:53.037 [pool-1-thread-6] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#25,pool-1-thread-6,5,main]
20:53:53.037 [pool-1-thread-7] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#26,pool-1-thread-7,5,main]
20:53:53.037 [pool-1-thread-8] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#27,pool-1-thread-8,5,main]
20:53:53.037 [pool-1-thread-9] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#28,pool-1-thread-9,5,main]
20:53:53.037 [pool-1-thread-4] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#23,pool-1-thread-4,5,main]
20:53:53.036 [pool-1-thread-3] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#22,pool-1-thread-3,5,main]
20:53:53.037 [pool-1-thread-5] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#24,pool-1-thread-5,5,main]
20:53:53.037 [pool-1-thread-10] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#29,pool-1-thread-10,5,main]
20:53:53.036 [pool-1-thread-2] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: Thread[#21,pool-1-thread-2,5,main]
20:53:54.042 [pool-1-thread-1] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#20,pool-1-thread-1,5,main]
20:53:54.043 [pool-1-thread-8] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#27,pool-1-thread-8,5,main]
20:53:54.044 [pool-1-thread-2] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#21,pool-1-thread-2,5,main]
20:53:54.045 [pool-1-thread-9] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#28,pool-1-thread-9,5,main]
20:53:54.045 [pool-1-thread-4] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#23,pool-1-thread-4,5,main]
20:53:54.045 [pool-1-thread-6] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#25,pool-1-thread-6,5,main]
20:53:54.045 [pool-1-thread-3] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#22,pool-1-thread-3,5,main]
20:53:54.046 [pool-1-thread-5] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#24,pool-1-thread-5,5,main]
20:53:54.046 [pool-1-thread-7] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#26,pool-1-thread-7,5,main]
20:53:54.042 [pool-1-thread-10] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: Thread[#29,pool-1-thread-10,5,main]
20:53:54.049 [main] INFO org.example.VirtualThreadExecutorCreation -- 2) main thread: Thread[#1,main,5,main]

 

10개의 쓰레드를 가지고 있는 쓰레드 풀 생성 후 override 한 runnable 실행하는 코드를 작성하였다.

 

'main 스레드와, executorService 스레드'는 별개 스레드이며, 각자 동작한다. 다만, executorService 스레드가 종료된 후 그 다음에 위치한 main 스레드를 실행시키고 싶다면 executorService.close()를 실행시키자. 

 

 

public static void main(String[] args) {
    log.info("1) main thread: " + Thread.currentThread());

    try (ExecutorService executorService = Executors.newFixedThreadPool(10)) {
        for (int i = 0; i < 10; i++) {
            executorService.submit(runnable);
        }
    }
    log.info("2) main thread: " + Thread.currentThread());
}

 

try로 묶어 준다면, executorService.close()를 별도로 실행시키지 않아도 된다. 이는 try 문이 끝날 때 자동으로 close()가 호출되기 때문이다. 

 

 

public interface ExecutorService extends Executor, AutoCloseable {

 

이것이 가능한 이유는 ExecutorService가 AutoCloseable를 상속받고 있기에 가능하다.

 

 

 

VirtualThread에서 Executor Service 사용하기

newVirtualThreadPerTaskExecutor()는 실행해야 할 때 마다 virtual thread를 만들어주는 것이다. 

 

@Slf4j
public class VirtualThreadExecutorCreation {
    private static final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            log.info("1) run. thread: {}", Thread.currentThread());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("2) run. thread: {}", Thread.currentThread());
        }
    };

    public static void main(String[] args) throws InterruptedException {
        log.info("1) main thread: " + Thread.currentThread());

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

        log.info("2) main thread: " + Thread.currentThread());
    }
}
21:11:38.294 [main] INFO org.example.VirtualThreadExecutorCreation -- 1) main thread: Thread[#1,main,5,main]
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#24]/runnable@ForkJoinPool-1-worker-4
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20]/runnable@ForkJoinPool-1-worker-1
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#28]/runnable@ForkJoinPool-1-worker-8
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#22]/runnable@ForkJoinPool-1-worker-2
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#29]/runnable@ForkJoinPool-1-worker-9
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#26]/runnable@ForkJoinPool-1-worker-6
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#30]/runnable@ForkJoinPool-1-worker-10
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#23]/runnable@ForkJoinPool-1-worker-3
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#27]/runnable@ForkJoinPool-1-worker-7
21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#25]/runnable@ForkJoinPool-1-worker-5
21:11:39.309 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20]/runnable@ForkJoinPool-1-worker-5
21:11:39.310 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#24]/runnable@ForkJoinPool-1-worker-4
21:11:39.310 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#28]/runnable@ForkJoinPool-1-worker-8
21:11:39.311 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#23]/runnable@ForkJoinPool-1-worker-4
21:11:39.311 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#27]/runnable@ForkJoinPool-1-worker-9
21:11:39.311 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#25]/runnable@ForkJoinPool-1-worker-7
21:11:39.310 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#22]/runnable@ForkJoinPool-1-worker-10
21:11:39.311 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#26]/runnable@ForkJoinPool-1-worker-3
21:11:39.311 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#30]/runnable@ForkJoinPool-1-worker-5
21:11:39.311 [] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#29]/runnable@ForkJoinPool-1-worker-6
21:11:39.311 [main] INFO org.example.VirtualThreadExecutorCreation -- 2) main thread: Thread[#1,main,5,main]

 

10개의 virtual thread를 만든다면 위와 같이 구현해 볼 수 있다.

 

 

21:11:38.300 [] INFO org.example.VirtualThreadExecutorCreation - 1) run. thread: VirtualThread[#24]/runnable@ForkJoinPool-1-worker-4

 

로그를 살펴보면 []에 스레드 이름이 찍혀있지 않다. newVirtualThreadPerTaskExecutor를 사용하면 스레드 이름을 사용하지 않게되어 있는것이 default로 되어 있기 때문이다. 

 

스레드 이름을 가지고 디버깅등 유용할 때가 많다. 스레드 이름을 설정하기 위해 ThreadFactory를 만들어주자.

 

 

public static void main(String[] args) throws InterruptedException {
    log.info("1) main thread: " + Thread.currentThread());

    ThreadFactory factory = Thread.ofVirtual().name("myVirtual-", 0).factory();
    //newThreadPerTaskExecutor 사용
    try (ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory)) {
        for (int i = 0; i < 10; i++) {
            executorService.submit(runnable);
        }
    }

    log.info("2) main thread: " + Thread.currentThread());
}
21:26:28.931 [main] INFO org.example.VirtualThreadExecutorCreation -- 1) main thread: Thread[#1,main,5,main]
21:26:28.939 [myVirtual-9] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#30,myVirtual-9]/runnable@ForkJoinPool-1-worker-10
21:26:28.939 [myVirtual-7] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#28,myVirtual-7]/runnable@ForkJoinPool-1-worker-8
21:26:28.938 [myVirtual-4] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#25,myVirtual-4]/runnable@ForkJoinPool-1-worker-5
21:26:28.938 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:26:28.939 [myVirtual-5] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#26,myVirtual-5]/runnable@ForkJoinPool-1-worker-6
21:26:28.938 [myVirtual-3] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#24,myVirtual-3]/runnable@ForkJoinPool-1-worker-4
21:26:28.939 [myVirtual-8] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#29,myVirtual-8]/runnable@ForkJoinPool-1-worker-9
21:26:28.938 [myVirtual-2] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#23,myVirtual-2]/runnable@ForkJoinPool-1-worker-3
21:26:28.939 [myVirtual-6] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#27,myVirtual-6]/runnable@ForkJoinPool-1-worker-7
21:26:28.938 [myVirtual-1] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#22,myVirtual-1]/runnable@ForkJoinPool-1-worker-2
21:26:29.946 [myVirtual-7] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#28,myVirtual-7]/runnable@ForkJoinPool-1-worker-1
21:26:29.947 [myVirtual-4] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#25,myVirtual-4]/runnable@ForkJoinPool-1-worker-1
21:26:29.947 [myVirtual-8] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#29,myVirtual-8]/runnable@ForkJoinPool-1-worker-1
21:26:29.947 [myVirtual-9] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#30,myVirtual-9]/runnable@ForkJoinPool-1-worker-1
21:26:29.948 [myVirtual-2] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#23,myVirtual-2]/runnable@ForkJoinPool-1-worker-1
21:26:29.948 [myVirtual-3] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#24,myVirtual-3]/runnable@ForkJoinPool-1-worker-1
21:26:29.948 [myVirtual-6] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#27,myVirtual-6]/runnable@ForkJoinPool-1-worker-1
21:26:29.948 [myVirtual-5] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#26,myVirtual-5]/runnable@ForkJoinPool-1-worker-1
21:26:29.948 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:26:29.948 [myVirtual-1] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#22,myVirtual-1]/runnable@ForkJoinPool-1-worker-1
21:26:29.949 [main] INFO org.example.VirtualThreadExecutorCreation -- 2) main thread: Thread[#1,main,5,main]

 

 결론적으로 executorService를 이용한 virtualThread 사용법은 위와 같다.

 

 

1) ThreadFactory 사용

2) newThreadPerTaskExecutor 사용

3) try 로 묶어주기 

 

 

 

안티 패턴을 살펴보자.

private static void antiPattern1() {
    ThreadFactory factory = Thread.ofVirtual().name("myVirtual-", 0).factory();
    try (ExecutorService executorService = Executors.newFixedThreadPool(1,factory)) {
        for (int i = 0; i < 10; i++) {
            executorService.submit(runnable);
        }
    }
}
21:35:56.493 [main] INFO org.example.VirtualThreadExecutorCreation -- 1) main thread: Thread[#1,main,5,main]
21:35:56.499 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:35:57.504 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:35:57.505 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:35:58.507 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:35:58.508 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:35:59.510 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-2
21:35:59.511 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-2
21:36:00.517 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:36:00.518 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:36:01.522 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-2
21:36:01.523 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-2
21:36:02.527 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:36:02.528 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:36:03.533 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:36:03.534 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:36:04.537 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:36:04.538 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:36:05.543 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:36:05.543 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:36:06.549 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-5
21:36:06.551 [main] INFO org.example.VirtualThreadExecutorCreation -- 2) main thread: Thread[#1,main,5,main]

 

위 코드는 virtualThread를 1개로 설정한 코드이다. virtualThread는 기본적으로 threadPool로 사용하지 말 것을 명시하고 있다. 가볍게 사용하기 위해 필요할 때마다 만들고 다 썻으면 버릴 수 있어야 하기 때문이다. 성능 또한 떨어진다.

 

 

 

private static void antiPattern2() {
    ThreadFactory factory = Thread.ofVirtual().name("myVirtual-", 0).factory();
    try (ExecutorService executorService = Executors.newSingleThreadExecutor(factory)) {
        for (int i = 0; i < 10; i++) {
            executorService.submit(runnable);
        }
    }
}
21:38:51.559 [main] INFO org.example.VirtualThreadExecutorCreation -- 1) main thread: Thread[#1,main,5,main]
21:38:51.566 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:38:52.573 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:38:52.574 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:38:53.578 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:53.578 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:54.588 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:54.588 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:55.594 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:55.594 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:56.599 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:56.600 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:57.605 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:57.606 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:58.608 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:38:58.609 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-1
21:38:59.616 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:38:59.617 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:39:00.624 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-3
21:39:00.625 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 1) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-3
21:39:01.630 [myVirtual-0] INFO org.example.VirtualThreadExecutorCreation -- 2) run. thread: VirtualThread[#20,myVirtual-0]/runnable@ForkJoinPool-1-worker-4
21:39:01.632 [main] INFO org.example.VirtualThreadExecutorCreation -- 2) main thread: Thread[#1,main,5,main]

 

singleThreadExecutor는 newFixedThreadPool(1) 과 동일하다. 이 또한, 한 개짜리 virtual thread pool 이기 때문에 사용하지 말아야 한다.