하드웨어 스레드:
커널 스레드:
User Thread:
Context Switching이 비용이 많이 드는 이유:
JDK 21 이전 방식:

기본 방식 문제점:
용량 비교
| 구분 | 커널 스레드 | 플랫폼 스레드 | 가상 스레드 |
|---|---|---|---|
| 크기 | ~8 KB | ~1 MB | 수 KB |

커널 스레드와 플랫폼 스레드가 1대1 mapping인지 확인:
import java.nio.file.Files;
import java.nio.file.Paths;
public class ThreadTest {
private static int getThreadCount() throws Exception {
return (int) Files.list(Paths.get("/proc/self/task")).count();
}
public static void main(String[] args) throws Exception {
System.out.println("Before = " + getThreadCount()); // 18
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
}
}).start();
}
Thread.sleep(1000); // 생성될 시간 주기
System.out.println("After = " + getThreadCount()); // 28
Thread.sleep(60000);
}
}
Virtual Thead:
Virtual Thread I/O 작업:
hello.world.virtualthread.purejava.VirtualThreadExecutorsCreation -- 1) run. thread: VirtualThread[#28,myVirtual-6]/runnable@ForkJoinPool-1-worker-3
...
hello.world.virtualthread.purejava.VirtualThreadExecutorsCreation -- 2) run. thread: VirtualThread[#28,myVirtual-6]/runnable@ForkJoinPool-1-worker-7
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
@Slf4j
public class VirtualThreadExecutorsCreation {
private static final Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("1) run. thread: " + Thread.currentThread()); // 시작과 끝의 thread가 다르다.
try {
Thread.sleep(3000);
} 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());
ThreadFactory factory = Thread.ofVirtual().name("myVirtual-", 0).factory(); // 권장 코드
try (ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory)) {
for (int i = 0; i < 10; i++) {
executorService.submit(runnable);
}
}
log.info("2) main. thread: " + Thread.currentThread());
}
// Virtual Thread의 toString 메서드
@Override
public String toString() {
StringBuilder sb = new StringBuilder("VirtualThread[#");
sb.append(threadId());
String name = getName();
if (!name.isEmpty()) {
sb.append(",");
sb.append(name);
}
sb.append("]/");
Thread carrier = carrierThread;
if (carrier != null) {
// include the carrier thread state and name when mounted
synchronized (carrierThreadAccessLock()) {
carrier = carrierThread;
if (carrier != null) {
String stateAsString = carrier.threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT));
sb.append('@');
sb.append(carrier.getName());
}
}
}
// include virtual thread state when not mounted
if (carrier == null) {
String stateAsString = threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT));
}
return sb.toString();
}
fork/join pool:
runContinuation:
cpu bound vs i/o bound:
가상 스레드가 carrier thread에서 분리될 수 없는 경우:
* virtual thread가 carrier thread에서 분리될 수 없는 상태가 된 경우 pinned 상태가 되었다고 한다.
(1) native method를 호출하는 경우
(2) synchronized 메서드 or block을 호출한 경우
- run configuration에서 "add VM Option" 클릭 후 "-Djdk.tracePinnedThreads=full or -Djdk.tracePinnedThreads=short 를 통해 detect" 둘중 하나 입력하여 detect
(3) parallelStream을 사용할 경우
해결책: ReentrantLock 사용해서 synchronized 부분에 lock.lock()과 lock.unlock()으로 동기화를 해주면 된다.
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReentrantLockMain {
private final ReentrantLock lock = new ReentrantLock();
// -Djdk.tracePinnedThreads=full or -Djdk.tracePinnedThreads=short 를 통해 detect
private final Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("1) run. thread: " + Thread.currentThread());
lock.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
log.info("2) run. thread: " + Thread.currentThread());
}
};
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
log.info("1) main. thread: " + Thread.currentThread());
// platform(); // 5019
virtual(); // 5021
log.info("2) main. time: " + (System.currentTimeMillis()-startTime) + " , thread: " + Thread.currentThread());
}
private static void virtual() {
ThreadFactory factory = Thread.ofVirtual().name("myVirtual-", 0).factory();
try (ExecutorService executorService = Executors.newThreadPerTaskExecutor(factory)) {
for (int i = 0; i < 20; i++) {
ReentrantLockMain pinning = new ReentrantLockMain();
executorService.submit(pinning.runnable);
}
}
}
private static void platform() {
try (ExecutorService executorService = Executors.newFixedThreadPool(20)) {
for (int i = 0; i < 20; i++) {
ReentrantLockMain pinning = new ReentrantLockMain();
executorService.submit(pinning.runnable);
}
}
}
}