스레드와 달리 JVM이 관리하는 경량 스레드로, 수백만 개의 스레드를 생성해도 메모리와 성능에 큰 영향을 주지 않습니다.
// Virtual Thread 생성
Thread.startVirtualThread(() -> {
System.out.println("Virtual Thread 실행");
});
// ExecutorService 사용
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// 작업 수행
});
}
Virtual Thread가 Carrier Thread에 고정(pin)되어 다른 작업을 수행할 수 없는 상황이 발생할 수 있습니다.
Pinning이 발생하는 경우:
synchronized 블록 내에서 blocking 작업 수행// ❌ 나쁜 예: synchronized 내에서 blocking
synchronized(lock) {
// Virtual Thread가 pinning됨
Thread.sleep(1000);
}
// ✅ 좋은 예: ReentrantLock 사용
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// Virtual Thread가 정상적으로 unmount 가능
Thread.sleep(1000);
} finally {
lock.unlock();
}
Virtual Thread는 수백만 개가 생성될 수 있으므로 ThreadLocal 사용 시 메모리 누수 위험이 있습니다.
// ❌ 위험: 대량의 Virtual Thread에서 ThreadLocal 사용
ThreadLocal<LargeObject> threadLocal = new ThreadLocal<>();
// ✅ 대안: ScopedValue 사용 (Java 21+)
ScopedValue<String> scopedValue = ScopedValue.newInstance();
Virtual Thread는 I/O 바운드 작업에 최적화되어 있습니다.
// ❌ Virtual Thread로는 비효율적
Thread.startVirtualThread(() -> {
// CPU 집약적 계산
for (int i = 0; i < 1_000_000_000; i++) {
// 복잡한 계산
}
});
// ✅ CPU 작업은 Platform Thread 풀 사용
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
Virtual Thread는 풀링하지 않고 작업마다 새로 생성해야 합니다.
// ❌ 나쁜 예: Virtual Thread 풀링
ExecutorService pool = Executors.newFixedThreadPool(100,
Thread.ofVirtual().factory());
// ✅ 좋은 예: 작업마다 생성
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
jcmd 명령어로 Virtual Thread 덤프 확인 가능일부 라이브러리가 Virtual Thread와 완벽히 호환되지 않을 수 있습니다:
Semaphore는 공유 자원에 대한 접근을 제어하는 동기화 도구입니다. 특정 개수의 스레드만 동시에 자원에 접근할 수 있도록 제한합니다.
// 3개의 허가를 가진 Semaphore 생성
Semaphore semaphore = new Semaphore(3);
// 자원 접근
try {
semaphore.acquire(); // 허가 획득
try {
// 공유 자원 사용
System.out.println("작업 수행");
} finally {
semaphore.release(); // 허가 반환
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
public class ConnectionPool {
private final Semaphore semaphore;
public ConnectionPool(int maxConnections) {
this.semaphore = new Semaphore(maxConnections);
}
public Connection getConnection() throws InterruptedException {
semaphore.acquire();
try {
return createConnection();
} catch (Exception e) {
semaphore.release();
throw e;
}
}
public void releaseConnection(Connection conn) {
closeConnection(conn);
semaphore.release();
}
}
Semaphore binarySemaphore = new Semaphore(1);
// Mutex처럼 사용 가능
Semaphore countingSemaphore = new Semaphore(10);
// 10개까지 동시 접근 허용
// Unfair (기본값) - 성능이 좋지만 공정성 보장 안됨
Semaphore unfairSemaphore = new Semaphore(5);
// Fair - 대기 순서대로 허가 부여
Semaphore fairSemaphore = new Semaphore(5, true);
Virtual Thread와 Semaphore를 함께 사용하면 대량의 요청을 처리하면서도 외부 자원(DB, API 등)에 대한 접근을 제한할 수 있습니다.
public class ApiClient {
private final Semaphore rateLimiter = new Semaphore(10); // 동시 10개 요청 제한
public void makeRequest(String url) {
Thread.startVirtualThread(() -> {
try {
rateLimiter.acquire();
try {
// API 호출
HttpClient.newHttpClient()
.send(HttpRequest.newBuilder()
.uri(URI.create(url))
.build(),
HttpResponse.BodyHandlers.ofString());
} finally {
rateLimiter.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
public class DatabaseService {
private final Semaphore dbSemaphore = new Semaphore(20); // DB 연결 20개 제한
public CompletableFuture<Result> query(String sql) {
return CompletableFuture.supplyAsync(() -> {
try {
dbSemaphore.acquire();
try {
return executeQuery(sql);
} finally {
dbSemaphore.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}, Executors.newVirtualThreadPerTaskExecutor());
}
}
tryAcquire(timeout) 사용으로 무한 대기 방지