/**
* 기본 Thread
*/
Thread thread = new Thread(runnable);
thread.run();
/**
* Virtual Thread
*/
Thread virtual_thread = Thread.ofVirtual().name("Virtual-Thread").start(runnable);
virtual_thread.join();
package sample.virtual_thread.pure;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.extern.slf4j.Slf4j;
@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());
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(runnable);
}
log.info("[2] main thread : {}", Thread.currentThread());
}
}
여기서 실행결과의 중간에 보이는 main thread를 executorService 부분이 다 끝나고 실행되게 해주려면
executorService.close();
을 추가하면 된다.
그리고 ExecutorService를 보면 AutoClosable을 extend하고 있는 형태이다.
AutoCloseable
은 Java 7에서 도입된 인터페이스로, try-with-resources 구문을 사용할 때 자원을 자동으로 닫을 수 있게 해주는 것이다. AutoCloseable
을 구현한 객체는 try-with-resources 구문을 통해 자동으로 close()
메서드가 호출되어 자원을 안전하게 해제할 수 있습니다.
그래서 이전에 작성하였던 코드를 아래와 같이 바꾸면, 굳이 수동으로 executorService.close() 를 추가하지 않아도 된다.
[이전코드]
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.submit(runnable);
}
executorService.close();
[수정된 코드]
try (ExecutorService executorService = Executors.newFixedThreadPool(10)) {
for (int i = 0; i < 10; i++) {
executorService.submit(runnable);
}
}
이렇게 바꿀 수 있다.
그러면, 이제 VirtualThread에서 사용하는 방법은 간단하다.
ThreadFactory factory = Thread.ofVirtual().name("Virtual_Thread", 0).factory();
try (ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory)) {
for (int i = 0; i < 100; i++) {
executorService.submit(runnable);
}
}
100개의 Virtual Thread를 생성한 코드이다. 또한, 각 Virtual Thread 별로 이름을 부여해서 고유 번호를 로그를 통해 확인하게 하였다. 이때 ThreadFactory를 이용할 수 있다.
ThreadFactory
는 새로운 스레드를 생성하는 방식을 커스터마이즈할 수 있는 인터페이스이다. Java의 java.util.concurrent 패키지에 포함되어 있으며, 기본 스레드 생성 방식이 아닌 사용자 정의 방식으로 스레드를 생성하고 싶을 때 사용할 수 있다.
Virtual Thread는 가벼운 성격의 Thread(한번 쓰고 버리는?)이기 때문에 Thread Pool을 만들 필요가 없다.
private static void antiPattern_first() {
ThreadFactory factory = Thread.ofVirtual().name("Virtual_Thread", 0).factory();
try (ExecutorService executorService = Executors.newFixedThreadPool(1, factory)) {
for (int i = 0; i < 100; i++) {
executorService.submit(runnable);
}
}
}
이것도 위와 똑같은 의미의 코드이다.
private static void antiPattern_first() {
ThreadFactory factory = Thread.ofVirtual().name("Virtual_Thread", 0).factory();
try (ExecutorService executorService = Executors.newSingleThreadExecutor(factory)) {
for (int i = 0; i < 100; i++) {
executorService.submit(runnable);
}
}
}
Virtual Threads는 제한된 고정 스레드 풀이나 단일 스레드 Executor로 묶어서 사용하는 것이 적합하지 않다는 것이다.
대신 Virtual Thread를 사용할 때는 Executors.newThreadPerTaskExecutor(factory)
와 같은 방식을 사용해 각 작업이 개별 Virtual Thread에서 실행되도록 하는 것이 좋다.
아래는 ForkJoinPool이 쓰이는 것을 보기 위한 예제 코드이다.
package sample.virtual_thread.pure;
import java.util.List;
import java.util.Optional;
public class ForkJoinPoolSample {
public static void main(String[] args) {
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Optional<Integer> optional = list.parallelStream()
.filter(integer -> {
System.out.println("integer : " + integer + ", thread : " + Thread.currentThread() + ", deamon : " + Thread.currentThread().isDaemon());
return integer % 2 == 0;
})
.findAny();
System.out.println(optional.get());
}
}
일반 stream()이 아닌, parallelStream() 즉 병렬 Stream을 사용했을 때의 결과이다. 일반 Stream을 사용했을 때와 다르게, 결과가 계속 바뀐다.(병렬이니깐 여러 Thread에서 돌기 때문에)
아래는 Pinned Virtual Thread에 대한 Oracle 문서이다.
public void run() {
synchronized (this) {
log.info("[1] Run Thread: {}", Thread.currentThread());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("[2] Run Thread: {}", Thread.currentThread());
}
}
synchronized (this)
: 이 블록은 synchronized
키워드를 사용하여 동기화된 코드 블록을 만든다.Thread.sleep(5000)
: Virtual Thread가 5초 동안 대기하게 된다. 일반적으로 Virtual Thread는 Blocking 작업 중에 Platform Thread에서 Unmount될 수 있지만, synchronized
Block 안에서는 Unmount 되지 않고 Platform Thread에 고정(Pinned)된 상태로 유지된다.native
라는 키워드가 적혀있는 것을 볼 수 있다. native
키워드는 Java에서 네이티브 메서드를 정의할 때 사용되는데, native 메서드는 Java가 아닌 다른 언어로 작성된 메서드를 호출할 때 사용되며, 주로 C나 C++와 같은 언어로 작성된 코드를 호출하는 데 쓰인다.이어서 작성중...
친절하면서도 섬세한 설명...