java 21 ๋ถํฐ preview๊ธฐ๋ฅ์ผ๋ก ์์ํ ๋ฒ์ ์ง์ ๊ฐ(์ดํ scoped value) API๊ฐ java 25๋ถํฐ ์ ์ ๊ธฐ๋ฅ์ผ๋ก ์ฑํ๋์๋ค.
Java Scoped Value API๋ ๋ฉ์๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ฉ์๋์ ์์ ํ๊ณ ํจ์จ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ํ๋ค.
ํนํ java21 ๋ถํฐ ๋์
๋ ๊ฐ์ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ ๋ ThreadLocal์ ์ฌ์ฉํ๋ฉด์ ๋ฐ์ํ ์ ์๋ ๋ฌธ์ ์ ๋ํด์ scoped value ๊ธฐ๋ฅ์ ThreadLocal์ ๋ํ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ ์ ์์ ๊ฒ์ด๋ค.
์ด๋ฒ ํฌ์คํ
์์๋ scoped value์ ๋ํ ๋์
๋ฐฐ๊ฒฝ๊ณผ ThreadLocal ์ฌ์ฉ์ ๋ํ ๋ฌธ์ ์ ํจ๊ป scoped value๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์ ๋ฆฌํด ๋ณด๊ณ ์ ํ๋ค. ์ํ์ฝ๋๋ Java21 (preview)๋ก ์์ฑํ์๋ค.
Java Scoped Value History
- scoped value์ ๋ํ ๊ธฐ๋ฅ์ Java 20 ๋ฒ์ ์์ ์ฒ์ ์ธํ๋ฒ ์ดํ ๋์๋ค. (JEP 429
- scoped value์ ๋ํ ๊ธฐ๋ฅ์ Java 21 ๋ฒ์ ์์ preview ๊ธฐ๋ฅ์ผ๋ก ๋ณ๊ฒฝ๋์๋ค. (JEP 446)
- scoped value์ ๋ํ ๊ธฐ๋ฅ์ Java 22 ๋ฒ์ ์์ 2nd preive๋ก API๊ฐ ๊ฐ์ ๋์๋ค. (JEP 464)
- scoped value์ ๋ํ ๊ธฐ๋ฅ์ Java 23 ๋ฒ์ ์์ 3rd preview๋ก call() ๋ฉ์๋๋ฑ ์ฌ์ฉ์ฑ์ด ๊ฐ์ ๋์๋ค. (JEP 481)
- scoped value์ ๋ํ ๊ธฐ๋ฅ์ Java 24 ๋ฒ์ ์์ 4th preview๋ก ๋ง์ง๋ง ๊ฒ์ฆ์ ๊ฑฐ์ณค๋ค. (JEP 487)
- scoped value์ ๋ํ ๊ธฐ๋ฅ์ Java 25 ๋ฒ์ ์์ ๋ง์ง๋ง์ผ๋ก ์ ์๋ฒ์ ์ผ๋ก ์ฑํ๋์๋ค. (JEP 506)
๋์ ๋ฐฐ๊ฒฝ
- ์ฌ์ฉ ํธ์์ฑ
- ๋ฐ์ดํฐ ํ๋ฆ์ ๋ํ ์ถ๋ก ์ ๊ฐ์ํํ ์ ์๋๋ก ์ค๋ ๋ ๋ฐ ํ์ ์ค๋ ๋์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ํ๋ก๊ทธ๋๋ฐ ๋ชจ๋ธ์ ์ ๊ณตํ๋ค.
- ์ดํด ๊ฐ๋ฅ์ฑ
- ์ฝ๋์ ๊ตฌ๋ฌธ ๊ตฌ์กฐ์์ ๊ณต์ ๋ฐ์ดํฐ์ ์๋ช ์ ํ์ธํ ์ ์๋ค.
- ๊ฒฌ๊ณ ์ฑ
- caller๊ฐ ๊ณต์ ํ ๋ฐ์ดํฐ๋ ํธ์ถ๋ callee์์๋ง ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค.
- ์ฑ๋ฅ
- ์ฌ๋ฌ ์ค๋ ๋์ ๊ฑธ์ณ์ ๊ณต์ ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก(Immutable)ํ์ฌ ๋ฐํ์ ์ต์ ํ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ค.
์ค๋ ๋ ๋ก์ปฌ ๋ณ์ ๋ฌธ์
JDK 1.2๋ถํฐ ๋์ ๋ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์๋ ๋ค์๊ณผ ๊ฐ์ ์ค๊ณ์ ์ธ ๊ฒฐํจ์ด ์๋ค.
- ์ ์ฝ ์๋ ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ
- ๋ชจ๋ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๋ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ค.
- ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ get() ๋ฉ์๋๋ฅผ ํธ์ถํ ์ ์๋ ์ฝ๋๋ ์ธ์ ๋ ์ง ํด๋น ๋ณ์์ set() ๋ฉ์๋๋ฅผ ํตํด ๋ณ๊ฒฝ ๊ฐ๋ฅํ๋ค.
- ์ด๋ก ์ธํด ๋ณต์กํ ๋ฐ์ดํฐ ํ๋ฆ์ด ๋ฐ์ํ ์ ์๊ณ ์ด๋ค ๋ฉ์๋๊ฐ ๊ณต์ ์ํ๋ฅผ ์ด๋ค ์์๋ก ์ ๋ฐ์ดํธํ๋์ง ํ์ ํ๊ธฐ ์ด๋ ต๊ฒ ํ๋ค.
- ๋ฌด์ ํ ์๋ช
- ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ ์ค๋ ๋ ๋ณต์ฌ๋ณธ์ด set() ๋ฉ์๋๋ฅผ ํตํด ์ค์ ๋๋ฉด ์ค์ ๋ ๊ฐ์ ์ค๋ ๋ ์๋ช ๋์ ๋๋ ์ค๋ ๋ ์ฝ๋๊ฐ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ remove() ๋ฉ์๋๋ฅผ ํธ์ถํ ๋๊น์ง ์ ์ง๋๋ค.
- ํนํ ์ค๋ ๋ ํ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ค๋ ๋ ์คํ์ด ์๋ฃ๋๋ฉด ๋ช ์์ ์ผ๋ก remove()๋ฅผ ํธ์ถํด์ผ ํ๋๋ฐ ์ด๋ฅผ ๋๋ฝํ ์ค์ ์ํ์ด ์๋ค. remove() ํธ์ถ์ด ๋๋ฝ๋๋ฉด ๊ด๋ จ ์๋ ์ค๋ ๋์ ํ์คํฌ๋ก ์ ๋ณด๊ฐ ์ ์ถ๋์ด ์ ์ฌ์ ์ผ๋ก ์ํํ ๋ณด์ ์ทจ์ฝ์ ์ด ๋ฐ์ํ ์ ์๋ค.
- ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ, ์ค๋ ๋๊ฐ ์ข
๋ฃ๋ ๋๊น์ง ์ค๋ ๋๋ณ ๋ฐ์ดํฐ์ ๋ํด์ ๊ฐ๋น์ง ์ปฌ๋ ์
์ด ์ํ๋์ง ์๊ธฐ ๋๋ฌธ์
remove()๋ฅผ ํธ์ถํ๋๋ผ๋ ์์ ํ ์์ ์ด ๋ช ํํ์ง ์์ ์ ์์ผ๋ฉฐ ์ด๋ก ์ธํด ์ฅ๊ธฐ์ ์ธ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ ์ ์๋ค.
- ๋น์ผ ์์ ๋น์ฉ
- ๋ถ๋ชจ ์ค๋ ๋์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๊ฐ ์์ ์ค๋ ๋์ ์์๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ ์ค๋ฒํค๋๋ ๋ง์ ์์ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ ๋ ๋ ์ฌํด์ง ์ ์๋ค.
- ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๊ฐ ์์๋๋ฉด ์์ ์ค๋ ๋๋ ๋ถ๋ชจ ์ค๋ ๋์ ๋ก์ปฌ ๋ณ์์ ๋ํ ๋ณ๋์ ์คํ ๋ฆฌ์ง๋ฅผ ํ ๋นํ์ฌ ์ฌ์ฉํ๊ฒ ๋๋๋ฐ ์ด๋ก ์ธํด ์๋นํ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ด ์๋น๋ ์ ์๋ค.
- ์์ ์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ ๋ํ ์คํ ๋ฆฌ์ง๋ฅผ ๋ณ๋๋ก ํ ๋นํ์ฌ ์ฌ์ฉํ๋ ์ด์ ๋ ์์ ์ค๋ ๋์์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๋ฅผ ๋ณ๊ฒฝํ ์ ์๊ธฐ ๋๋ฌธ์ (์ ์ฝ ์๋ ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ) ์ด๋ ๋ถ๋ชจ ์ค๋ ๋์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ ์ํฅ์ ์ฃผ์ง ์๊ธฐ ์ํจ์ด๋ค.
- ๋ณดํต์ ์์ ์ค๋ ๋์์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์๋ฅผ ๋ณ๊ฒฝํ๋ ์ผ์ ํํ์ง ์์์๋ ์ ์ฝ ์๋ ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ผ๋ก ์ธํ์ฌ ๋ถํ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น๋ฅผ ํ๋ ๊ฒ์ด๋ค.
Scoped Value
scoped value API ๋ํ ๋ช ๊ฐ์ง ํน์ง๊ณผ ์ฌ์ฉ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์.
- scoped value๋ ์๋ฌต์ ์ธ ๋ฉ์๋์ ๋งค๊ฐ๋ณ์์ด๋ค.
- scoped value๋ immutable ํ ์ธ์คํด์ค๋ฅผ ๊ณต์ ํจ์ผ๋ก์จ ๋ฐ์ดํฐ์ ๋ํ ๋ฌด๊ฒฐ์ฑ์ ์ ๊ณตํ๋ค.
- ์ค๋ ๋ ํน์ ๋ฉ์๋ ๋ด์์ ํธ์ถ๋๋ ๋ชจ๋ ๋ฉ์๋ ํธ์ถ์ ์๋์ผ๋ก ๋ฐ์ธ๋ฉ๋๋ค.
- ์ค๋ ๋ ํน์ ๋ฉ์๋๊ฐ ์ข ๋ฃ๋๋ฉด ์๋์ผ๋ก ๋ฐ์ธ๋ฉ์ ํด์ ๋๋ค.
- ๋ถ๋ชจ ์ค๋ ๋์์ ์ ์ค๋ ๋๋ฅผ ํฌํฌ(์์ ์ค๋ ๋)ํ๋ฉด ๋ชจ๋ ์์ ์ค๋ ๋๊ฐ ์๋์ผ๋ก scoped value์ ์ก์ธ์ค ํ ์ ์๋ค.
- scoped value๋ ์๋ฌต์ ์ธ ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ผ๋ ์ธก๋ฉด์์ ๋ณด๋ฉด scoped value ๋ณ์์ ์ฐธ์กฐ๋ง ๋ณต์ฌ๋๊ณ ์ ์ธ์คํด์ค๋ฅผ ์์ฑํ์ง ์๋๋ค๋ ์ ์์ ๋ฉ๋ชจ๋ฆฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค. ์ด๋ ์๋ง์ ๊ฐ์ ์ค๋ ๋๊ฐ ์คํ๋๋ ํ๊ฒฝ์์ ๊ณผ๋ํ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ์ฉ์ ๋ฐฉ์งํด ์ค ์ ์๋ค.
์ํ ์ฝ๋
JDK21 Preview ํ์ฑํ (Java 25๋ฅผ ์ฌ์ฉํ๋ค๋ฉด Language level๋ง 25๋ก ๋ง์ถ๋ฉด ๋๊ฒ ๋ค.)
scoped value API๋ JDK21์์ preview ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์ Intellij IDE์์๋ ๋ค์๊ณผ ๊ฐ์ ์ค์ ์ด ํ์ํ๋ค.
SDK๋ JDK21์ ์ง์ ํด์ผ ํ๋ค. ์ํ ์ฝ๋๋ temurin JDK 21 ๋ฒ์ ์ ์ค์นํ์ฌ ์ง์ ํ์๋ค.
Language level์ 21(Preview)์ ์ค์ ํด์ผ ํ๋๋ฐ Intellij 2023.2.5 ๋ฒ์ ์ด์์์ ์ง์ํ๋ค.

ํน์ ์คํ ์ –enable-preview ์ต์ ์ ์ง์ ํ์ฌ ํ์ฑํํ ์ ์๋ค.
์ผ๋ฐ์ ์ฌ์ฉ
scoped value API ํ์ ์ ScopedValue ํ์ ์ด๋ค. scoped value API์ ์ผ๋ฐ์ ์ธ ์ฌ์ฉ ์ฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
//scoped value ์ธ์คํด์ค ์์ฑ
final static ScopedValue<ํ์
> scopedValue = ScopedValue.newInstance();
//'SCOPED' ๊ฐ์ด scopedValue์ ๋ฐ์ธ๋ฉ๋์ด task()๋ฅผ ํธ์ถํ๋ค.
//task() ๋ฉ์๋์์๋ 'SCOPED' ๊ฐ์ access ํ ์ ์๋ค.
ScopedValue.where(scopedValue, "SCOPED").run(() -> task());
//where.run ํธ์ถ์ ํ๋๋ก ํธ์ถํ ์ ์๋ค.
ScopedValue.runWhere(scopedValue, "SCOPED", () -> task());
//'SCOPED' ๊ฐ์ด scopedValue์ ๋ฐ์ธ๋ฉ๋์ด callTask()๋ฅผ ํธ์ถํ๋ค.
//callTask()์์๋ 'SCOPED' ๊ฐ์ access ํ ์ ์๋ค.
//callTask() ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํด ๋ฐ์ ์ ์๋ค.
<callTask ๋ฆฌํดํ์
> = ScopedValue.where(scopedValue, "SCOPED").call(() -> callTask());
//where.call ํธ์ถ์ ํ๋๋ก ํธ์ถํ ์ ์๋ค.
<callTask ๋ฆฌํดํ์
> = ScopedValue.callWhere(scopedValue, "SCOPED", () -> callTask());
//scopedValue ๊ฐ ๋ฐ์ธ๋ฉ ๋์๋์ง ํ์ธํ ์ ์๋ค.
boolean isBound = scopedValue.isBound();
//scopedValue ์ ๋ฐ์ธ๋ฉ ๋ ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
String boundedValue = scopedValue.get();
//scopedValue์ ๋ฐ์ธ๋ฉ๋ ๊ฐ์ด ์๋ค๋ฉด 'not bounded' ๊ฐ์ ๋ฆฌํดํ๋ค.
String boundedValue = scopedValue.orElse("not bounded");
//scopedValue์ ๋ฐ์ธ๋ฉ๋ ๊ฐ์ด ์๋ค๋ฉด ์์ธ๋ฅผ ๋ฐ์์ํจ๋ค.
String boundedValue = scopedValue.orElseThrow(() -> new Exception("not bounded"));
Javarun(), runWhere(),call(), callWhere() ํธ์ถ์ ์๋ก์ด ์ค๋ ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ์๋๋ค.
์ผ๋ฐ ๋ฉ์๋์์ ์ฌ์ฉ
import java.util.concurrent.Executors;
import java.util.concurrent.StructuredTaskScope;
public class Main {
final static ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
public static void main(String[] args) {
normalMethod();
}
private static void normalMethod() {
ScopedValue.where(SCOPED_VALUE, "task-out1").run(Main::method);
System.out.println("method() after scoped value isBound: "
+ SCOPED_VALUE.isBound());
}
private static void method() {
long threadId = Thread.currentThread().threadId();
System.out.println("method, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
subMethod(threadId);
System.out.println("method end thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
}
private static void subMethod() {
long threadId = Thread.currentThread().threadId();
System.out.println("sub method, parent thread id: " + parentThreadId +
", thread id: " + threadId + ", scoped value: " + SCOPED_VALUE.get());
}
}Javamethod, thread id: 1, scoped value: task-out1
sub method, parent thread id: 1, thread id: 1, scoped value: task-out1
method end thread id: 1, scoped value: task-out1
method() after scoped value isBound: falsePlaintextSCOPED_VALUE scoped value ๋ณ์๋ ‘task-out1’ ๊ฐ์ ๋ฐ์ธ๋ฉํ์ฌ method() -> subMethod()๋ฅผ ํธ์ถํ์๋ค.
method() after scoped value isBound: false ์ถ๋ ฅ์ ํตํด method()๊ฐ ์ข
๋ฃ๋๋ฉด ๋ฐ์ธ๋ฉ์ ํด์ ๋จ์ ์ ์ ์๋ค.
SCOPED_VALUE์ ํ ๋น๋ ‘task-out1’ ๊ฐ์ scope๋ ๋ค์๊ณผ ๊ฐ๋ค.
+-- scoped value(task-out1)
|
| +-- method()
| |
| | +-- subMethod()
| | |
| | |__
| |
| |__
|
|__Plaintexttask-out1 scoped value์ scope์ method(), subMethod()์์ access ํ ์ ์๋ค.
Rebinding scoped value
scoped value๋ immutable์ด์ง๋ง ๋ค๋ฅธ ๊ฐ์ ์ ๋ฌํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ ์ ์๋๋ฐ ์ด ๊ฒฝ์ฐ์๋ ์ค์ฒฉ ๋ฐ์ธ๋ฉ์ ํตํด์ ํด๊ฒฐํ ์ ์๋ค.
์ฝ๋๋ฅผ ํตํด์ ์์๋ณด์.
import java.util.concurrent.Executors;
import java.util.concurrent.StructuredTaskScope;
public class Main {
final static ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
public static void main(String[] args) {
normalMethod();
}
private static void normalMethod() {
ScopedValue.where(SCOPED_VALUE, "task-out1").run(Main::method);
System.out.println("method() after scoped value isBound: "
+ SCOPED_VALUE.isBound());
}
private static void method() {
long threadId = Thread.currentThread().threadId();
System.out.println("method, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
ScopedValue.where(SCOPED_VALUE, "submethod " + SCOPED_VALUE.get())
.run(() -> subMethod(threadId));
System.out.println("method end thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
}
private static void subMethod() {
long threadId = Thread.currentThread().threadId();
System.out.println("sub method, parent thread id: " + parentThreadId +
", thread id: " + threadId + ", scoped value: " + SCOPED_VALUE.get());
}
}Javamethod, thread id: 1, scoped value: task-out1
sub method, parent thread id: 1, thread id: 1, scoped value: submethod task-out1
method end thread id: 1, scoped value: task-out1
method() after scoped value isBound: falsePlaintextmethod() ๋ด์์ subMethod()๋ฅผ ํธ์ถํ ๋ SCOPED_VALUE ๋ณ์์ ‘submethod task-out1’ ๊ฐ์ ๋ฐ์ธ๋ฉํ์ฌ subMethod()๋ฅผ ํธ์ถํ์๋ค.
method()์์๋ SCOPED_VALUE์ ‘task-out1’ ๊ฐ์ด ๋ฐ์ธ๋ฉ๋์๊ณ subMethod()๋ฅผ ํธ์ถํ๊ธฐ ์ ์ SCOPED_VALUE์ ‘submethod task-out1’ ๊ฐ์ ๋ฐ์ธ๋ฉํ์ฌ ํธ์ถํ์๋ค.
์ด๋ ๊ฐ ๋ฉ์๋์ ๋ํ SCOPED_VALUE์ ๋ํ scope๋ ๋ค์๊ณผ ๊ฐ๋ค.
+-- scoped value(task-out1)
| +-- method()
| | +-- scoped value(submethod task-out1)
| | | +-- subMethod()
| | | |
| | | |__
| | |__
| |__
|__PlaintextsubMethod()๋ ‘submethod task-out1’์ด ๋ฐ์ธ๋ฉ๋ SCOPED_VALUE๋ฅผ access ํ๋ค.
method()๋ ‘task-out1’์ด ๋ฐ์ธ๋ฉ๋ SCOPED_VALUE๋ฅผ access ํ๋ค.
๊ฐ์ ์ค๋ ๋์์ (virtual thread) ์ฌ์ฉ
์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ ๋ง์ฐฌ๊ฐ์ง๋ก ScopedValue๋ฅผ ํตํด์ ์ค๋ ๋ ๋ด์์๋ง ์ ์ง๋ ์ ์๋ ๊ฐ์ ์ ๋ฌํ ์ ์๋ค.
์ํ ์ฝ๋๋ฅผ ํตํด์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์์๋ณด์.
import java.util.concurrent.Executors;
import java.util.concurrent.StructuredTaskScope;
public class Main {
final static ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
public static void main(String[] args) {
virtualThread();
}
private static void virtualThread() {
try (var executorService = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 5; i++) {
final int temp = i;
executorService.submit(() ->
ScopedValue.where(SCOPED_VALUE, "task" + temp)
.run(Main::task));
}
}
}
private static void task() {
long threadId = Thread.currentThread().threadId();
System.out.println("task, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
childTask(threadId);
}
private static Void childTask(long parentThreadId) {
String scopedValue = SCOPED_VALUE.orElse("not setted");
long threadId = Thread.currentThread().threadId();
System.out.println("child task, parent thread id: " + parentThreadId +
", thread id: " + threadId + ", scoped value: " + scopedValue);
return null;
}
}Javatask, thread id: 23, scoped value: task1
task, thread id: 24, scoped value: task2
task, thread id: 21, scoped value: task0
task, thread id: 25, scoped value: task3
task, thread id: 26, scoped value: task4
child task, parent thread id: 23, thread id: 23, scoped value: task1
child task, parent thread id: 21, thread id: 21, scoped value: task0
child task, parent thread id: 26, thread id: 26, scoped value: task4
child task, parent thread id: 25, thread id: 25, scoped value: task3
child task, parent thread id: 24, thread id: 24, scoped value: task2Plaintexttask() ๋ฉ์๋๋ ๊ฐ์ ์ค๋ ๋์ ํ์คํฌ ๋ฉ์๋๋ค. task() ๋ฉ์๋์์ childTask()๋ฅผ ํธ์ถํ๋ฉด์ childTask() ์๋ ์๋์ผ๋ก ๋ฐ์ธ๋ฉ๋ scoped value ๊ฐ์ด ์ ๋ฌ๋๋ค. ๊ฐ์ ์ค๋ ๋์ task() ์ญ์ childTask()๋ฅผ ํธ์ถํ ๋ scoped value๋ฅผ rebinding ๊ฐ๋ฅํ๋ค.
์ํ ์ฝ๋์ ๊ฐ์ ์ค๋ ๋ ๋ด์์ scoped value์ scope๋ ๋ค์๊ณผ ๊ฐ๋ค.
+-- virtual thread
| +-- scoped value(task-out-number)
| | +-- task()
| | | +-- childTask()
| | | |
| | | |__
| | |__
| |__
|__Plaintextscoped value ์์
๋ถ๋ชจ ์ค๋ ๋์ ์์ ์ค๋ ๋ ๊ฐ์ scoped value ์์์ ๊ตฌ์กฐํ๋ ๋์์ฑ API(Structured Concurrency API)๋ฅผ ํตํด์ ์์ฑ๋ ์์ ์ค๋ ๋์๊ฒ๋ง ์๋์ผ๋ก ์์๋๋ค. ์ฐธ๊ณ ๋ก JDK 25์์ Structured Concurrency ์ฌ์ฉ ๋ฌธ๋ฒ์ด ์ฝ๊ฐ ๋ณ๊ฒฝ๋์๋ค.
์๋ ์ํ์ฝ๋๋ JDK 21 ๋ฒ์ ์์์ Structured Concurrency ์ฌ์ฉ๋ฒ์์ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค.
(Structured Concurrency์ ๋ํด์๋ ์ค๋ ๋ ๋์๋ ์ด์ ๊ทธ๋ง! Structured Concurrency ์๊ฐ (ft. JDK 25) ์ฐธ๊ณ )
์ํ ์ฝ๋๋ฅผ ํตํด์ ํ์ธํด ๋ณด์.
private static void task() {
long threadId = Thread.currentThread().threadId();
System.out.println("task, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
scope.fork(() -> childTask(threadId));
scope.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static Void childTask(long parentThreadId) {
String scopedValue = SCOPED_VALUE.orElse("not setted");
long threadId = Thread.currentThread().threadId();
System.out.println("child task " + threadId + " is virtual: " +
Thread.currentThread().isVirtual());
System.out.println("child task, parent thread id: " + parentThreadId +
", thread id: " + threadId + ", scoped value: " + scopedValue);
return null;
}Javatask() ๋ฉ์๋์์ childTask()๋ฅผ ๋จ์ ํธ์ถ ํ๋ ๊ฒ์ด ์๋ childTask()๋ฅผ fork() ํ์ฌ ์์ ์ค๋ ๋์ ํ์คํฌ๋ก ๋์ํ๋๋ก ์์ฑํ์๋ค.
๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
task, thread id: 26, scoped value: task4
task, thread id: 23, scoped value: task1
task, thread id: 21, scoped value: task0
task, thread id: 25, scoped value: task3
task, thread id: 24, scoped value: task2
child task 36 is virtual: true
child task 33 is virtual: true
child task 32 is virtual: true
child task 34 is virtual: true
child task 35 is virtual: true
child task, parent thread id: 24, thread id: 36, scoped value: task2
child task, parent thread id: 25, thread id: 32, scoped value: task3
child task, parent thread id: 26, thread id: 34, scoped value: task4
child task, parent thread id: 21, thread id: 33, scoped value: task0
child task, parent thread id: 23, thread id: 35, scoped value: task1Plaintexttask()์ ์์ ์ค๋ ๋์ธ childTask()์๋ ๋์ผํ scoped value๊ฐ ์์๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
StructuredTaskScope ๊ฐ ์๋ ์ผ๋ฐ์ ์ธ ์ค๋ ๋ ์์ฑ ๋ฐฉ๋ฒ (Thread, ExecutorService..)์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์์ฑ๋ ์์ ์ค๋ ๋์๋ scoped value๊ฐ ์์๋์ง ์๋๋ค.
task() ๋ด์์ Thread ์ธ์คํด์ค๋ฅผ ํตํด์ ์์ ์ค๋ ๋๋ฅผ ์์ฑํ๋ ๊ฒฝ์ฐ scoped value๊ฐ ์์ ์ค๋ ๋์ ์ ๋ฌ๋๋์ง ์ฌ๋ถ๋ฅผ ํ์ธํด ๋ณด์.
private static void task() {
long threadId = Thread.currentThread().threadId();
System.out.println("task, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
Thread thread = Thread.ofVirtual().start(() -> childTask(threadId));
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static Void childTask(long parentThreadId) {
String scopedValue = SCOPED_VALUE.orElse("not setted");
long threadId = Thread.currentThread().threadId();
System.out.println("child task " + threadId + " is virtual: " +
Thread.currentThread().isVirtual());
System.out.println("child task, parent thread id: " + parentThreadId +
", thread id: " + threadId + ", scoped value: " + scopedValue);
return null;
}Java์ ์ฝ๋์ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
task, thread id: 24, scoped value: task2
task, thread id: 25, scoped value: task3
task, thread id: 21, scoped value: task0
task, thread id: 23, scoped value: task1
task, thread id: 26, scoped value: task4
child task 36 is virtual: true
child task 37 is virtual: true
child task 33 is virtual: true
child task 34 is virtual: true
child task 35 is virtual: true
child task, parent thread id: 25, thread id: 37, scoped value: not setted
child task, parent thread id: 23, thread id: 34, scoped value: not setted
child task, parent thread id: 26, thread id: 35, scoped value: not setted
child task, parent thread id: 21, thread id: 36, scoped value: not setted
child task, parent thread id: 24, thread id: 33, scoped value: not settedPlaintextscoped value๋ ‘not setted’๋ก ์ถ๋ ฅ๋์ด scoped value๊ฐ ๋ถ๋ชจ ์ค๋ ๋๋ก๋ถํฐ ์์๋์ง ์์์ ์ ์ ์๋ค.
๋ถ๋ชจ ์ค๋ ๋์์ ์์ ์ค๋ ๋๋ก scoped value๊ฐ ์์๋๋ ค๋ฉด ์์ ์ค๋ ๋๋ ๊ตฌ์กฐํ๋ ๋์์ฑ API(StructuredTaskScope)๋ฅผ ํตํด์ ์์ฑ๋ ์ค๋ ๋์ฌ์ผ ํ๋ค.
์์ ์ค๋ ๋ ์์ฑ ์ scoped value rebinding
์์ ์ค๋ ๋์๊ฒ scoped value๋ฅผ ์์์ด ์๋ ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ฐ์ธ๋ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ๋ค.
private static void task() {
long threadId = Thread.currentThread().threadId();
System.out.println("task, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
//๊ตฌ์กฐํ๋ ๋์์ฑ API(StructuredTaskScope)๋ฅผ ํตํด์ ์์ฑ๋ ์์์ค๋ ๋์๊ฒ scoped value๋ ์์๋๋ค.
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
scope.fork(
() -> ScopedValue.callWhere(
SCOPED_VALUE,
"child task " + SCOPED_VALUE.get(),
() -> childTask(threadId)
));
scope.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("task end, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
}Javatask, thread id: 26, scoped value: task4
task, thread id: 25, scoped value: task3
task, thread id: 24, scoped value: task2
task, thread id: 23, scoped value: task1
task, thread id: 21, scoped value: task0
child task 35 is virtual: true
child task 34 is virtual: true
child task 36 is virtual: true
child task 33 is virtual: true
child task 32 is virtual: true
child task, parent thread id: 21, thread id: 36, scoped value: child task task0
child task, parent thread id: 24, thread id: 35, scoped value: child task task2
child task, parent thread id: 25, thread id: 34, scoped value: child task task3
child task, parent thread id: 26, thread id: 32, scoped value: child task task4
child task, parent thread id: 23, thread id: 33, scoped value: child task task1
task end, thread id: 25, scoped value: task3
task end, thread id: 24, scoped value: task2
task end, thread id: 23, scoped value: task1
task end, thread id: 26, scoped value: task4
task end, thread id: 21, scoped value: task0Plaintexttask() ๋ฉ์๋ ๋ด์์ scoped value๋ ‘task<num>’ ์ผ๋ก ๋ฐ์ธ๋ฉ ๋์์ง๋ง childTask() ๋ฉ์๋ ๋ด์์๋ ‘child task task<num>’ ๊ฐ์ผ๋ก rebinding ๋์ด์ ์ฌ์ฉ๋๋ค.
์ ํต์ ์ธ ์ค๋ ๋(platform thread)์์ ์ฌ์ฉ
๊ฐ์ ์ค๋ ๋์์ ์ฌ์ฉ๋ ์ฉ๋ฒ๊ณผ ํฌ๊ฒ ๋ค๋ฅด์ง ์๋ค. ๋์ ์ค๋ ๋๊ฐ platform ์ด๋ virtual ์ด๋์ ์ฐจ์ด๋ค.
์ํ ์ฝ๋๋ฅผ ํตํด์ ํ์ธํด ๋ณด์.
import java.util.concurrent.Executors;
import java.util.concurrent.StructuredTaskScope;
public class Main {
final static ScopedValue<String> SCOPED_VALUE = ScopedValue.newInstance();
public static void main(String[] args) {
platformThread();
}
private static void platformThread() {
try (var executorService = Executors.newFixedThreadPool(5)) {
for (int i = 0; i < 5; i++) {
final int temp = i;
executorService.submit(() ->
ScopedValue.where(SCOPED_VALUE, "platform" + temp)
.run(Main::platformTask));
}
}
}
private static void platformTask() {
long threadId = Thread.currentThread().threadId();
System.out.println("platform task, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
//์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ์์ ์ค๋ ๋๋ฅผ ์์ฑํ๋ฉด scoped value๋ ์์๋์ง ์๋๋ค.
//new Thread(() -> platformChildTask(threadId)).start();
//๊ตฌ์กฐํ๋ ๋์์ฑ API(StructuredTaskScope๋ฅผ ํตํด์ ์์ฑ๋ ์์ ์ค๋ ๋์๊ฒ scoped value๊ฐ ์์๋๋ค.)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
scope.fork(() -> platformChildTask(threadId));
scope.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//platformTask() ๋ด์์ ํธ์ถ๋๋ ๋ฉ์๋์๋ scoped value๊ฐ ์๋ ๋ฐ์ธ๋ฉ ๋๋ค.
//platformChildTask(threadId);
System.out.println("platform task end, thread id: " + threadId +
", scoped value: " + SCOPED_VALUE.get());
}
private static Void platformChildTask(long parentThreadId) {
long threadId = Thread.currentThread().threadId();
System.out.println("child platform task " + threadId +
" is virtual: " + Thread.currentThread().isVirtual());
String scopedValue = SCOPED_VALUE.orElse("not setted");
System.out.println("child platform task, parent thread id: " + parentThreadId +
", thread id: " + threadId + ", scoped value: " + scopedValue);
return null;
}
}Java๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค.
platform task, thread id: 21, scoped value: platform0
platform task, thread id: 24, scoped value: platform3
platform task, thread id: 22, scoped value: platform1
platform task, thread id: 23, scoped value: platform2
platform task, thread id: 25, scoped value: platform4
child platform task 27 is virtual: true
child platform task 28 is virtual: true
child platform task 29 is virtual: true
child platform task 26 is virtual: true
child platform task 30 is virtual: true
child platform task, parent thread id: 23, thread id: 26, scoped value: platform2
child platform task, parent thread id: 22, thread id: 30, scoped value: platform1
child platform task, parent thread id: 24, thread id: 28, scoped value: platform3
child platform task, parent thread id: 25, thread id: 27, scoped value: platform4
child platform task, parent thread id: 21, thread id: 29, scoped value: platform0
platform task end, thread id: 23, scoped value: platform2
platform task end, thread id: 21, scoped value: platform0
platform task end, thread id: 25, scoped value: platform4
platform task end, thread id: 24, scoped value: platform3
platform task end, thread id: 22, scoped value: platform1Plaintextplatform ์ค๋ ๋ ๋ด์์๋ scoped value๋ฅผ ์์ ์ค๋ ๋์๊ฒ ์์ํ๊ธฐ ์ํด์๋ ๊ตฌ์กฐํ๋ ๋์์ฑ API(StructuredTaskScope)๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑํด์ผ ํ๋ค. ๊ตฌ์กฐํ๋ ๋์์ฑ API๋ฅผ ํตํด์ ์์ฑ๋ ์ค๋ ๋๋ ๊ฐ์ ์ค๋ ๋์์ ์ ์ ์๋ค.
์ง๊ธ๊น์ง Java 21์์ preview(Java 25์์ ์ ์) ๊ธฐ์ค์ผ๋ก scoped value API์ ๋ํ ์ฌ์ฉ๋ฒ์ ์ ๋ฆฌํด ๋ณด์๋ค. scoped value๋ ๊ธฐ์กด์ ์ค๋ ๋ ๋ก์ปฌ ๋ณ์์ ๋ํ ์ค๊ณ์ ์ธ ๋ฌธ์ ์ ๋ค์ ๊ฐ์ ํ์๊ณ ์ด๋ฅผ ํตํด virtual thread ๋ด์์ ThreadLocal์ ๋ํ ๋์์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์์ ํ ๊ฒ์ด๋ค.
์ฐธ๊ณ ๋งํฌ
https://www.baeldung.com/java-20-scoped-values
https://www.baeldung.com/java-structured-concurrency
https://howtodoinjava.com/java/multi-threading/java-scoped-values/
