Java 25 - Scoped Values (Final)
업데이트:
JAVA 251
Scoped Values(범위가 지정된 값)2는 Java 25에서 최종 기능으로 제공되는 API입니다. 이 기능은 메서드가 호출하는 다른 메서드들과 같은 스레드 내에서, 그리고 자식 스레드와 불변 데이터를 공유할 수 있도록 합니다. ThreadLocal에 비해 더 효율적이고 안전합니다.
Scoped Values vs ThreadLocal
ThreadLocal은 오랫동안 사용되었지만 몇 가지 문제가 있습니다:
- 무제한의 가변성: 언제든지
set()메서드로 값을 변경할 수 있음 - 무한한 생명주기:
remove()호출을 잊으면 메모리 누수 발생 - 높은 상속 비용: 자식 스레드가 부모 스레드의 모든 ThreadLocal값을 복사해야 함
Scoped Values는 다음을 제공합니다:
- 불변성: 한 번 설정되면 변경할 수 없음
- 제한된 생명주기: 스코프를 벗어나면 자동으로 소멸
- 효율적인 상속: 메모리 복사 없이 자식 스레드와 공유
기본 사용 방법
1. Scoped Value 선언 및 기본 사용
import java.lang.ScopedValue;
import static java.lang.ScopedValue.where;
public class BasicScopedValueExample {
// Scoped Value 선언
private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
private static final ScopedValue<Integer> USER_ID = ScopedValue.newInstance();
public static void main(String[] args) {
// 값을 바인딩하여 코드 실행
where(USERNAME, "Alice")
.where(USER_ID, 101)
.run(() -> {
System.out.println("사용자: " + USERNAME.get());
System.out.println("ID: " + USER_ID.get());
performUserOperation();
});
}
private static void performUserOperation() {
System.out.println("사용자 작업 수행 - " + USERNAME.get());
}
}
실행 결과:
사용자: Alice
ID: 101
사용자 작업 수행 - Alice
프레임워크 컨텍스트 전달 예제
2. 웹 프레임워크에서의 사용
import java.lang.ScopedValue;
import static java.lang.ScopedValue.where;
public class FrameworkContext {
// 프레임워크 컨텍스트
static class RequestContext {
private final String requestId;
private final String userId;
private final long timestamp;
RequestContext(String requestId, String userId) {
this.requestId = requestId;
this.userId = userId;
this.timestamp = System.currentTimeMillis();
}
public String getRequestId() { return requestId; }
public String getUserId() { return userId; }
public long getTimestamp() { return timestamp; }
}
// 프레임워크에서 관리하는 Scoped Value
private static final ScopedValue<RequestContext> CONTEXT =
ScopedValue.newInstance();
// 프레임워크의 요청 처리
public static void handleRequest(String requestId, String userId) {
RequestContext context = new RequestContext(requestId, userId);
where(CONTEXT, context).run(() -> {
// 애플리케이션 코드 실행
Application.processRequest();
// 데이터베이스 쿼리
Framework.queryDatabase();
});
}
// 데이터 접근 계층
static class Framework {
public static void queryDatabase() {
// CONTEXT.get()을 통해 컨텍스트 접근
RequestContext ctx = CONTEXT.get();
System.out.println("DB 조회 - 요청ID: " + ctx.getRequestId()
+ ", 사용자: " + ctx.getUserId());
}
}
// 애플리케이션 코드
static class Application {
public static void processRequest() {
RequestContext ctx = CONTEXT.get();
System.out.println("요청 처리 - 사용자: " + ctx.getUserId());
}
}
public static void main(String[] args) {
handleRequest("REQ-001", "user123");
}
}
실행 결과:
요청 처리 - 사용자: user123
DB 조회 - 요청ID: REQ-001, 사용자: user123
값의 재바인딩
3. 중첩된 스코프에서 값 재바인딩
import java.lang.ScopedValue;
import static java.lang.ScopedValue.where;
public class RebindingScopedValue {
private static final ScopedValue<String> CONTEXT = ScopedValue.newInstance();
public static void main(String[] args) {
where(CONTEXT, "parent").run(() -> {
System.out.println("부모 스코프: " + CONTEXT.get());
outerMethod();
// 다시 부모 스코프의 값으로 복원됨
System.out.println("부모 스코프 (복원): " + CONTEXT.get());
});
}
private static void outerMethod() {
// 새로운 값으로 재바인딩
where(CONTEXT, "child").run(() -> {
System.out.println("자식 스코프: " + CONTEXT.get());
innerMethod();
System.out.println("자식 스코프 (복원): " + CONTEXT.get());
});
}
private static void innerMethod() {
where(CONTEXT, "grandchild").run(() -> {
System.out.println("손자 스코프: " + CONTEXT.get());
});
}
}
실행 결과:
부모 스코프: parent
자식 스코프: child
손자 스코프: grandchild
자식 스코프 (복원): child
부모 스코프 (복원): parent
가상 스레드3와의 결합
4. Structured Concurrency4와 통합
import java.lang.ScopedValue;
import java.util.concurrent.StructuredTaskScope;
import static java.lang.ScopedValue.where;
public class ScopedValueWithStructuredConcurrency {
private static final ScopedValue<String> USER_ID =
ScopedValue.newInstance();
public static void main(String[] args) throws InterruptedException {
where(USER_ID, "user-123").run(() -> {
try (var scope = StructuredTaskScope.open()) {
// 자식 스레드들이 USER_ID 값을 상속받음
scope.fork(() -> {
System.out.println("작업 1 - 사용자: " + USER_ID.get());
return "작업 1 완료";
});
scope.fork(() -> {
System.out.println("작업 2 - 사용자: " + USER_ID.get());
return "작업 2 완료";
});
scope.join();
System.out.println("모든 작업 완료");
} catch (StructuredTaskScope.FailedException e) {
e.printStackTrace();
}
});
}
}
실행 결과:
작업 1 - 사용자: user-123
작업 2 - 사용자: user-123
모든 작업 완료
값의 바인딩 여부 확인
5. isBound()를 이용한 확인
import java.lang.ScopedValue;
import static java.lang.ScopedValue.where;
public class CheckBindingExample {
private static final ScopedValue<String> SETTING =
ScopedValue.newInstance();
public static void main(String[] args) {
// 바인딩 없이
checkSetting();
// 바인딩을 통해
where(SETTING, "enabled").run(() -> {
checkSetting();
});
}
private static void checkSetting() {
if (SETTING.isBound()) {
System.out.println("설정값: " + SETTING.get());
} else {
System.out.println("설정값이 바인딩되지 않음");
}
}
}
실행 결과:
설정값이 바인딩되지 않음
설정값: enabled
복수 값 바인딩
6. 여러 Scoped Value 한 번에 바인딩
import java.lang.ScopedValue;
import static java.lang.ScopedValue.where;
public class MultipleValuesExample {
private static final ScopedValue<String> USERNAME =
ScopedValue.newInstance();
private static final ScopedValue<Integer> ROLE =
ScopedValue.newInstance();
private static final ScopedValue<String> TENANT =
ScopedValue.newInstance();
public static void main(String[] args) {
// 여러 값을 한 번에 바인딩
where(USERNAME, "Alice")
.where(ROLE, 5)
.where(TENANT, "company-x")
.run(() -> {
displayUserInfo();
});
}
private static void displayUserInfo() {
System.out.println("사용자명: " + USERNAME.get());
System.out.println("역할: " + (ROLE.get() == 5 ? "관리자" : "사용자"));
System.out.println("테넌트: " + TENANT.get());
}
}
실행 결과:
사용자명: Alice
역할: 관리자
테넌트: company-x
호출 vs 실행
7. call()을 사용한 값 반환
import java.lang.ScopedValue;
import static java.lang.ScopedValue.where;
public class CallReturnValueExample {
private static final ScopedValue<Integer> MULTIPLIER =
ScopedValue.newInstance();
public static void main(String[] args) throws Exception {
int result = where(MULTIPLIER, 10).call(() -> {
return calculate(5);
});
System.out.println("결과: " + result);
}
private static int calculate(int value) {
return value * MULTIPLIER.get();
}
}
실행 결과:
결과: 50
주요 특징 요약
| 특징 | Scoped Values | ThreadLocal |
|---|---|---|
| 불변성 | ✓ (변경 불가) | ✗ (set 호출 가능) |
| 생명주기 | 제한됨 (자동) | 무한 (수동 제거) |
| 메모리 효율 | 높음 | 낮음 |
| 자식 스레드 상속 | 효율적 | 비효율적 |
| API 복잡도 | 간단 | 복잡 |
마이그레이션 지침
ThreadLocal에서 Scoped Values로 마이그레이션할 때:
- 단방향 데이터 전달인 경우 Scoped Values 추천
- 양방향 통신이 필요한 경우 ThreadLocal 유지
- 메모리 효율이 중요한 경우 Scoped Values 필수
- 가상 스레드 사용 시 Scoped Values 권장
주의사항
.enable-preview플래그 필요:javac --enable-preview --release 25 ScopedValueExample.java java --enable-preview ScopedValueExampleScopedValue는 불변이므로 바인딩 후 변경 불가능- Structured Concurrency와 함께 사용할 때 가장 효율적
댓글남기기