ThreadLocal์˜ ๊ฐœ์„ : Java Scoped Value ๊ฐ€์ด๋“œ


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 ๋ฒ„์ „ ์ด์ƒ์—์„œ ์ง€์›ํ•œ๋‹ค.

intellij ํ”„๋กœ์ ํŠธ JDK 21 preivew ์„ค์ •

ํ˜น์€ ์‹คํ–‰ ์‹œ –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"));
Java

run(), 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());
    }
}
Java
method, 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: false
Plaintext

SCOPED_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()
| |  |
| |  |__
| |
| |__
|
|__
Plaintext

task-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());
    }
}
Java
method, 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: false
Plaintext

method() ๋‚ด์—์„œ 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()
| | |  |
| | |  |__
| | |__
| |__
|__
Plaintext

subMethod()๋Š” ‘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;
    }
}
Java
task, 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: task2
Plaintext

task() ๋ฉ”์„œ๋“œ๋Š” ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ์˜ ํƒœ์Šคํฌ ๋ฉ”์„œ๋“œ๋‹ค. task() ๋ฉ”์„œ๋“œ์—์„œ childTask()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ childTask() ์—๋Š” ์ž๋™์œผ๋กœ ๋ฐ”์ธ๋”ฉ๋œ scoped value ๊ฐ’์ด ์ „๋‹ฌ๋œ๋‹ค. ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ์˜ task() ์—ญ์‹œ childTask()๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ scoped value๋ฅผ rebinding ๊ฐ€๋Šฅํ•˜๋‹ค.
์ƒ˜ํ”Œ ์ฝ”๋“œ์˜ ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ ๋‚ด์—์„œ scoped value์˜ scope๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

+-- virtual thread
|  +-- scoped value(task-out-number)
|  |  +-- task()
|  |  |  +-- childTask()
|  |  |  |
|  |  |  |__
|  |  |__
|  |__
|__
Plaintext

scoped 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;
}
Java

task() ๋ฉ”์„œ๋“œ์—์„œ 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: task1
Plaintext

task()์˜ ์ž์‹ ์Šค๋ ˆ๋“œ์ธ 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 setted
Plaintext

scoped 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());
}
Java
task, 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: task0
Plaintext

task() ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ 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: platform1
Plaintext

platform ์Šค๋ ˆ๋“œ ๋‚ด์—์„œ๋„ 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/