์Šค๋ ˆ๋“œ ๋ˆ„์ˆ˜๋Š” ์ด์ œ ๊ทธ๋งŒ! Structured Concurrency ์†Œ๊ฐœ (ft. JDK 25)

Java ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์Šค๋ ˆ๋“œ ์‚ฌ์šฉ์€ ๋งค์šฐ ์ค‘์š”ํ•œ ์š”์†Œ๋‹ค. ๊ทธ๋™์•ˆ ExecutorService๋‚˜ CompletableFuture๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ธฐ๋Šฅ์ ์œผ๋กœ ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜ ๊ธฐ๋Šฅ์œผ๋กœ ๋ฌถ์ธ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ ์ค‘์—์„œ ํ•˜๋‚˜๋ผ๋„ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋‚˜๋จธ์ง€ ์Šค๋ ˆ๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ณต์žกํ•œ ์—๋Ÿฌ ๋กœ์ง์„ ์‚ฌ์šฉํ–ˆ์„ ๊ฒƒ์ด๋‹ค.(cancel() ํ˜ธ์ถœ..) ํ•˜์ง€๋งŒ ์ด๋ฒˆ์— ์†Œ๊ฐœํ•  Structured Concurrency๋Š” ์ด๋Ÿฌํ•œ ๊ณจ์น˜์•„ํ”ˆ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ์ค„ ๊ฒƒ์ด๋‹ค. ๋˜ํ•œ JDK21 ๋ฒ„์ „์—์„œ ์ •์‹ ๋ฆด๋ฆฌ์ฆˆ๋œ ๊ฐ€์ƒ ์Šค๋ ˆ๋“œ์— ๋งž์ถฐ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ ๋˜๋Š” ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์— ๋Œ€ํ•œ ๊ด€๋ฆฌ ์ธก๋ฉด์—์„œ๋„ ๋งŽ์€ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™๋‹ค.
์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ๊ธฐ์กด ๋ฐฉ์‹์ด ์–ด๋–ค ๋ถ€๋ถ„์—์„œ ์œ„ํ—˜ํ–ˆ๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  Structured Concurrency๊ฐ€ ์–ด๋–ป๊ฒŒ ์Šค๋ ˆ๋“œ ๋ˆ„์ˆ˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ์ฃผ๋Š”์ง€ ๋ฏธ๋ฆฌ ์•Œ์•„๋ณด์ž.


Structured Concurrency ํžˆ์Šคํ† ๋ฆฌ

์šฐ์„  ๊ฐ„๋‹จํžˆ Structured Concurrency์˜ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์•Œ์•„ ๋ณด์ž.

  • JEP428 JEP437 – JDK19 ์ฒ˜์Œ์œผ๋กœ ์ œ์•ˆ ๋˜์—ˆ๊ณ  JDK 20๊นŒ์ง€ Incubator ๋‹จ๊ณ„๋กœ ์‹œ์ž‘๋˜์—ˆ๋‹ค.
  • JEP453 – JDK21 Structured Concurrency๊ฐ€ Preview ์ƒํƒœ๋กœ ์Šน๊ฒฉ๋ฌ๋‹ค.
  • JEP462 – JDK22 ์—ฌ์ „ํžˆ Prewiew ์ƒํƒœ๋กœ API ์•ˆ์ •ํ™” ๋ฐ ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ˜์˜ํ•œ๋‹ค.
  • JEP480 – JDK23 Structured Concurrency์˜ ์„ธ๋ฒˆ์งธ Preview ์ƒํƒœ.
  • JEP499 – JDK24 API ๊ฐœ์„ , ๊ตฌ์กฐ ๊ฐœ์„ ์ด ์ด๋ฃจ์–ด์กŒ๋‹ค. ๋„ค๋ฒˆ์งธ Preview ์ƒํƒœ.
  • JEP505 – JDK25 API ์„ค๊ณ„๊ฐ€ ๋‹ค๋“ฌ์–ด์ง€๊ณ  ์ •์‹ ๋ฐฐํฌ ์ „ํ™˜์„ ์œ„ํ•œ ์•ˆ์ •ํ™” ๋‹จ๊ณ„๋กœ ํ‰๊ฐ€ ๋œ๋‹ค. ๋‹ค์„ฏ ๋ฒˆ์งธ Preview.
  • JEP525 – JDK26 ์ฐจ๊ธฐ ๋ฐฐํฌ ๋ฒ„์ „ ์˜ˆ์ •์ธ 26 ๋ฒ„์ „์—๋„ ์—ฌ์„ฏ ๋ฒˆ์งธ Preview๋กœ ์—ฐ๊ตฌ ์ค‘์ด๋‹ค.

์•„์ง Structured Concurrency๊ฐ€ ์•„์ง์€ Preview ๋‹จ๊ณ„๋กœ ์‹คํ—˜์ ์ด์ง€๋งŒ ์ถ”ํ›„์— ์ •์‹ ๊ธฐ๋Šฅ์œผ๋กœ ๋ฆด๋ฆฌ์ฆˆ ๋˜๋ฉด ์Šค๋ ˆ๋“œ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด์„œ ์‚ฌ์šฉํšจ๊ณผ๊ฐ€ ๊ฝค ํด ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€๋œ๋‹ค. JDK25 ๋ฒ„์ „์—์„œ API ์„ค๊ณ„๊ฐ€ ๋‹ค๋“ฌ์–ด ์ง„๋งŒํผ ์ด์ „ JDK21 ๋ฒ„์ „๊ณผ ๋น„๊ตํ•ด ์‚ฌ์šฉ๋ฒ•์ด ์ผ๋ถ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

Structured Concurrency ๋ณ€๊ฒฝ ํฌ์ธํŠธ

์ด์ „ JDK ๋ฒ„์ „์˜ ์‚ฌ์šฉ๋ฒ•๊ณผ JDK25์—์„œ์˜ ์‚ฌ์šฉ๋ฒ•์— ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ํด๋ž˜์Šค ์ƒ์† ์ œ๊ฑฐ: ShutdownOnFailure, ShutdownOnSuccess ๊ฐ™์€ ์ƒ์† ๊ธฐ๋ฐ˜ ํด๋ž˜์Šค๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ๋‹ค.
  • Factory Method ๋„์ž…: new StructuredTaskScope(…) ๋Œ€์‹  StructuredTaskScope.open(…)์„ ์‚ฌ์šฉํ•œ๋‹ค.
  • Joiner ๋„์ž…: ‘์‹คํŒจํ•˜๋ฉด ์ค‘๋‹จํ•  ๊ฒƒ์ธ๊ฐ€’, ‘์„ฑ๊ณตํ•œ ๊ฒƒ๋งŒ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ธ๊ฐ€’์™€ ๊ฐ™์€ ์ •์ฑ…์„ Joiner๋ผ๋Š” ๊ฐ์ฒด์— ์œ„์ž„ํ•œ๋‹ค.
  • join()์˜ ์ง„ํ™”: join() ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹จ์ˆœํžˆ ๋Œ€๊ธฐ๋งŒ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ Joiner๊ฐ€ ์ฒ˜๋ฆฌํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค. (throwIfFailed()๋ฅผ ํ˜ธ์ถœํ•  ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๋‹ค.)

๊ธฐ์กด ๋ฐฉ์‹์˜ ๋ฌธ์ œ์ 

๋ถ€๋ชจ ์ž์‹ ๊ฐ„์˜ ๊ด€๊ณ„ ๋‹จ์ ˆ

๊ธฐ์กด์˜ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ(Unstructured Concurrency)์˜ ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๋Š” ์ž‘์—… ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ๋Š๊ฒจ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค.
๋ถ€๋ชจ ์Šค๋ ˆ๋“œ์—์„œ ์ž์‹ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„์ด์ง€๋งŒ JVM ์ž…์žฅ์—์„œ๋Š” ์ด ๋‘˜์€ ์•„๋ฌด ์ƒ๊ด€์—†๋Š” ์Šค๋ ˆ๋“œ์ผ ๋ฟ์ธ ๊ฒƒ์ด๋‹ค.

public static void main() {
    legacy1();
    System.out.println("Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!");
}

static void legacy() {
    // ๊ธฐ์กด ๋ฐฉ์‹ (ExecutorService)
    // ๋ถ€๋ชจ๊ฐ€ ์ฃฝ์–ด๋„ ์ž์‹์€ ์ข€๋น„์ฒ˜๋Ÿผ ๊ณ„์† ๋ˆ๋‹ค.
  
    ExecutorService es = Executors.newFixedThreadPool(1);
  
    // ์ž์‹ ์Šค๋ ˆ๋“œ: 5์ดˆ ๊ฑธ๋ฆฌ๋Š” ๋ฌด๊ฑฐ์šด ์ž‘์—…
    Future<?> child = es.submit(() -> {
        try {
            Thread.sleep(5000);
            System.out.println("์ž์‹: ์ž‘์—… ์™„๋ฃŒ! (ํ•˜์ง€๋งŒ ์•„๋ฌด๋„ ๋“ฃ์ง€ ์•Š๋Š”๋‹ค..)");
        } catch (InterruptedException e) {
            // ์ทจ์†Œ ๋กœ์ง์ด ์žˆ์–ด๋„, ํ˜ธ์ถœ์„ ์•ˆ ํ•ด์ฃผ๋ฉด ๋ฌด์šฉ์ง€๋ฌผ
        }
    });
  
    try {
        // ๋ถ€๋ชจ ์Šค๋ ˆ๋“œ: ๊ฐ‘์ž‘์Šค๋Ÿฌ์šด ์—๋Ÿฌ ๋ฐœ์ƒ
        throw new RuntimeException("๋ถ€๋ชจ ์ข…๋ฃŒ!");
    }
    catch ( Exception e ) {
        System.out.println("์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ์ง€! ์‘๋‹ต์„ ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ข…๋ฃŒ!");
    }
  
    es.shutdown();
}
Java

์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ์ง€! ์‘๋‹ต์„ ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ข…๋ฃŒ!
Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!
.... 5์ดˆํ›„ ....
์ž์‹: ์ž‘์—… ์™„๋ฃŒ! (ํ•˜์ง€๋งŒ ์•„๋ฌด๋„ ๋“ฃ์ง€ ์•Š๋Š”๋‹ค..)
Plaintext

๋ถ€๋ชจ ์Šค๋ ˆ๋“œ์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์ข…๋ฃŒ๊ฐ€ ๋˜์–ด๋„ ์ž์‹์Šค๋ ˆ๋“œ๋Š” 5์ดˆ๊ฐ„ CPU๋ฅผ ์ ์œ ํ•˜๋ฉฐ ๊ณ„์†ํ•ด์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
์ด๊ฒƒ์ด ๋ฐ”๋กœ ์Šค๋ ˆ๋“œ ๋ˆ„์ˆ˜(Thread Leak)์ด๋‹ค.

ํ˜•์ œ ๊ด€๊ณ„๋„ ๋‹จ์ ˆ

๋ถ€๋ชจ ์ž์‹ ๊ด€๊ณ„๊ฐ€ ์•„๋‹Œ ์–ด๋–ค ์„œ๋กœ ์—ฐ๊ด€์„ฑ์ด ์žˆ๋Š” ๋‘ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌ๋˜๊ณ  ์žˆ๋‹ค๊ฐ€ ํ•ด๋ณด์ž. ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‹ค๋ฅธ ๋ชจ๋“  ์ฒ˜๋ฆฌ๋ฅผ ๋ฐ”๋กœ ์ข…๋ฃŒ์‹œํ‚ค๊ณ  ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ์— ์žˆ์–ด์„œ ๋” ์œ ๋ฆฌํ•  ๊ฒƒ์ด๋‹ค.

public static void main() {
    legacy2();
    System.out.println("Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!");
}

static void legacy2() {

    CompletableFuture<Void> taskA = CompletableFuture.runAsync( () -> {
        try {
            Thread.sleep( 100 );
            System.out.println("Task A: ์—๋Ÿฌ ๋ฐœ์ƒ!!");
            throw new RuntimeException( "Task A ์˜ˆ์™ธ ๋ฐœ์ƒ ");
        }
        catch ( InterruptedException e ) {
            throw new RuntimeException(e);
        }
    } );

    CompletableFuture<Void> taskB = CompletableFuture.runAsync( () -> {
        System.out.println("๋ฌด๊ฑฐ์šด ์ž‘์—… ์‹œ์ž‘ (5์ดˆ ์†Œ์š”)");
        try {
            Thread.sleep( 5000 );
            System.out.println("Task B: ์ž‘์—… ์™„๋ฃŒ! (ํ•˜์ง€๋งŒ ์•„๋ฌด๋„ ๋“ฃ์ง€ ์•Š๋Š”๋‹ค");
        }
        catch ( InterruptedException e ) {
            throw new RuntimeException( e );
        }
        throw new RuntimeException( "์˜ˆ์™ธ ๋ฐœ์ƒ!!!" );
    } );

    CompletableFuture<Void> all = CompletableFuture.allOf( taskA, taskB );
    try {
        all.join();
    }
    catch ( Exception e ) {
        System.out.println("์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ์ง€! ์‘๋‹ต์„ ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ข…๋ฃŒ!");
    }
}
Java

TaskA๊ณผ TaskB ๋‘ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ์„œ๋กœ ์—ฐ๊ด€๋œ ํ˜•์ œ ๊ด€๊ณ„๋ผ๊ณ  ํ•˜๋”๋ผ๋„ ํ•œ์ชฝ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฐ”๋กœ ์ข…๋ฃŒ ๋˜์ง€ ์•Š๊ณ  ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์‹คํ–‰ํ•˜๊ฒŒ ๋œ๋‹ค.
๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

๋ฌด๊ฑฐ์šด ์ž‘์—… ์‹œ์ž‘ (5์ดˆ ์†Œ์š”)
Task A: ์—๋Ÿฌ ๋ฐœ์ƒ!!
.... 5์ดˆํ›„ ....
Task B: ์ž‘์—… ์™„๋ฃŒ! (ํ•˜์ง€๋งŒ ์•„๋ฌด๋„ ๋“ฃ์ง€ ์•Š๋Š”๋‹ค)
์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ์ง€! ์‘๋‹ต์„ ์‹คํŒจ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ข…๋ฃŒ!
Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!
Plaintext

์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์€ TaskA์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด TaskB ์—ญ์‹œ ๋ฐ”๋กœ ์ข…๋ฃŒํ•˜๊ณ  ๋™์ž‘์„ ๋๋‚ด๋Š” ๊ฒƒ์ด์ง€๋งŒ TaskA์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ TaskB๋Š” ๊ณ„์†ํ•ด์„œ ์‹คํ–‰๋˜๊ณ  ์žˆ๋‹ค.


Structured Concurrency์˜ ๋™์ž‘

์ด์ œ Structured Concurrency์˜ ๋™์ž‘์„ ์‚ดํŽด๋ณด์ž.

๋ถ€๋ชจ ์ž์‹๊ฐ„์˜ ๊ด€๊ณ„ ์œ ์ง€

Structured Concurrency๋Š” ๋ถ€๋ชจ ์Šค๋ ˆ๋“œ์™€ ์ž์‹ ์Šค๋ ˆ๋“œ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์œ ์ง€ํ•จ์œผ๋กœ์จ ๋ถ€๋ชจ๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด ์ž์‹๋„ ๋ฐ”๋กœ ์ข…๋ฃŒ๋œ๋‹ค.

public static void main() {
    structuredscope1();
    System.out.println("Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!");
}

static void structuredscope1() {
    // try-with-resources๋กœ Scope ์ƒ์„ฑ (์ž๋™ close ๋ณด์žฅ)
    try ( var scope = StructuredTaskScope.open(
            StructuredTaskScope.Joiner.awaitAllSuccessfulOrThrow() ) ) {

        // 1. Fork: ์ž์‹ ์Šค๋ ˆ๋“œ ์ƒ์„ฑ (Scope์˜ ๊ด€๋ฆฌ๋ฅผ ๋ฐ›์Œ)
        StructuredTaskScope.Subtask<Void> fork = scope.fork( () -> {
            try {
                Thread.sleep( 5000 );
                System.out.println("์ž์‹: ์ž‘์—… ์™„๋ฃŒ!");
            }
            catch ( InterruptedException e ) {
                throw new RuntimeException( e );
            }
        } );

        // 2. ๋ถ€๋ชจ ๋กœ์ง์—์„œ ์—๋Ÿฌ ๋ฐœ์ƒ ๊ฐ€์ •
        if (true) throw new RuntimeException("๋ถ€๋ชจ ์—๋Ÿฌ!");

        scope.join();// ๋Œ€๊ธฐ
    } catch (Exception e) {
        System.out.println(e.getMessage());
        // 3. try ๋ธ”๋ก์„ ๋ฒ—์–ด๋‚˜๋Š” ์ˆœ๊ฐ„ scope.close()๊ฐ€ ํ˜ธ์ถœ๋จ.
        // -> ์‹คํ–‰ ์ค‘์ด๋˜ ์ž์‹ ์Šค๋ ˆ๋“œ(task)์— ์ฆ‰์‹œ 'Interruption' ์‹ ํ˜ธ๊ฐ€ ์ „์†ก๋จ.
        System.out.println("Scope๊ฐ€ ๋‹ซํžˆ๋ฉด์„œ ์ž์‹ ์Šค๋ ˆ๋“œ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
    }
}
Java

Joiner์˜ awaitAllSuccessfulOrThrow()๋Š” ๋ชจ๋“  ์ž์‹์ด ์„ฑ๊ณตํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค. StructuredTaskScope๋‚ด์˜ ์Šค๋ ˆ๋“œ๊ฐ€ 5์ดˆ ๋™์•ˆ ์ผ์„ ํ•˜๋Š” ๋™์•ˆ ๋ถ€๋ชจ ๋กœ์ง์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ๊ฒฐ๊ณผ๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณด์ž.

๋ถ€๋ชจ ์—๋Ÿฌ!
Scope๊ฐ€ ๋‹ซํžˆ๋ฉด์„œ ์ž์‹ ์Šค๋ ˆ๋“œ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!
Plaintext

๋ถ€๋ชจ๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด์„œ ์ž์‹ ์Šค๋ ˆ๋“œ๊ฐ€ ๋งˆ์ง€๋ง‰๊นŒ์ง€ ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์ข…๋ฃŒ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ˜•์ œ ๊ด€๊ณ„ ์œ ์ง€

StructuredTaskScope๋กœ ๋ฌถ์ธ ๋‘๊ฐœ์˜ ์Šค๋ ˆ๋“œ ์ž‘์—…์ด ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋  ๋•Œ ํ•˜๋‚˜์˜ ์Šค๋ ˆ๋“œ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ๋ฅผ ์‚ดํŽด๋ณด์ž.

public static void main() {
    structuredscope2();
    System.out.println("Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!");
}

    static void structuredscope2() {
        // try-with-resources๋กœ Scope ์ƒ์„ฑ (์ž๋™ close ๋ณด์žฅ)
        try ( var scope = StructuredTaskScope.open( 
                    StructuredTaskScope.Joiner.awaitAllSuccessfulOrThrow() ) ) {

            // 1. Fork: ์ž์‹ ์Šค๋ ˆ๋“œ ์ƒ์„ฑ (Scope์˜ ๊ด€๋ฆฌ๋ฅผ ๋ฐ›์Œ)
            StructuredTaskScope.Subtask<Void> taskA = scope.fork( () -> {
                try {
                    Thread.sleep( 300 );
                    System.out.println("Task A: ์—๋Ÿฌ ๋ฐœ์ƒ!!");
                    throw new RuntimeException( "Task A ์˜ˆ์™ธ ๋ฐœ์ƒ ");
                }
                catch ( InterruptedException e ) {
                    throw new RuntimeException( e );
                }
            } );

            StructuredTaskScope.Subtask<Void> taskB = scope.fork( () -> {
                System.out.println("๋ฌด๊ฑฐ์šด ์ž‘์—… ์‹œ์ž‘ (5์ดˆ ์†Œ์š”)");
                try {
                    Thread.sleep( 5000 );
                    System.out.println("Task B: ์ž‘์—… ์™„๋ฃŒ!");
                }
                catch ( InterruptedException e ) {
                    throw new RuntimeException( e );
                }
            } );

            scope.join();// ๋Œ€๊ธฐ
        } catch (Exception e) {
            System.out.println(e.getMessage());
            // 3. try ๋ธ”๋ก์„ ๋ฒ—์–ด๋‚˜๋Š” ์ˆœ๊ฐ„ scope.close()๊ฐ€ ํ˜ธ์ถœ๋จ.
            // -> ์‹คํ–‰ ์ค‘์ด๋˜ ์ž์‹ ์Šค๋ ˆ๋“œ(task)์— ์ฆ‰์‹œ 'Interruption' ์‹ ํ˜ธ๊ฐ€ ์ „์†ก๋จ.
            System.out.println("Scope๊ฐ€ ๋‹ซํžˆ๋ฉด์„œ ์ž์‹ ์Šค๋ ˆ๋“œ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        }
    }
Java

์•ž์„œ ์„ค๋ช…ํ–ˆ๋“ฏ์ด Joiner์˜ awaitAllSuccessfulOrThrow()๋Š” ๋ชจ๋“  ์ž์‹ ํ˜•์ œ ์Šค๋ ˆ๋“œ๊ฐ€ ์„ฑ๊ณตํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค. ํ˜•์ œ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ชจ๋“  ์Šค๋ ˆ๋“œ๋Š” ์ข…๋ฃŒํ•œ๋‹ค. ๊ฒฐ๊ณผ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•ด ๋ณด์ž.

๋ฌด๊ฑฐ์šด ์ž‘์—… ์‹œ์ž‘ (5์ดˆ ์†Œ์š”)
Task A: ์—๋Ÿฌ ๋ฐœ์ƒ!!
java.lang.RuntimeException: Task A ์˜ˆ์™ธ ๋ฐœ์ƒ 
Scope๊ฐ€ ๋‹ซํžˆ๋ฉด์„œ ์ž์‹ ์Šค๋ ˆ๋“œ๊ฐ€ ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
Main ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ!!
Plaintext

TaskA์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด TaskB์˜ ๋™์ž‘์ด ์ข…๋ฃŒ๋œ๋‹ค.


StructuredTaskScope.Joiner ์ •์ฑ…

StructuredTaskScope.Joiner๋Š” ์—ฌ๋Ÿฌ ์ž์‹ ์Šค๋ ˆ๋“œ์˜ ์˜ˆ์™ธ ๋ฐ ์™„๋ฃŒ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๋ช‡๊ฐ€์ง€ ์ •์ฑ…์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

  • awaitAllSuccessfulOrThrow()
    • ๋ชจ๋“  ์ž‘์—…์ด ์„ฑ๊ณตํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ๋งŒ์•ฝ ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•œ๋‹ค๋ฉด ์ฆ‰์‹œ ๋‚˜๋จธ์ง€ ์ž‘์—…์„ ์ทจ์†Œํ•˜๊ณ  ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค. join() ํ˜ธ์ถœ์‹œ ๊ฒฐ๊ณผ๋Š” ์—†๋‹ค.
    • ์„œ๋กœ ๋‹ค๋ฅธ ํƒ€์ž…์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๊ฒฐ๊ณผ๋Š” join()์ด ์•„๋‹Œ ๋ฏธ๋ฆฌ ์„ ์–ธํ•ด ๋‘” SubTask์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
// ๊ฒฐ๊ณผ๊ฐ’์ด ์—†์œผ๋ฏ€๋กœ Subtask ๋ณ€์ˆ˜์—์„œ get() ํ•ด์•ผ ํ•จ
try (var scope = StructuredTaskScope.open(Joiner.awaitAllSuccessfulOrThrow())) {
    Subtask<String> taskA = scope.fork(() -> "A");
    Subtask<Integer> taskB = scope.fork(() -> 1);
    
    scope.join(); // ์—ฌ๊ธฐ์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ
    // taskA.get(), taskB.get() ์‚ฌ์šฉ
}
Java
  • allSuccessfulOrThrow()
    • awaitAllSuccessfulOrThrow์™€ ๊ฐ™์ด ๋™์ž‘ํ•˜์ง€๋งŒ join() ํ˜ธ์ถœ์‹œ ์„ฑ๊ณตํ•œ ๊ฒฐ๊ณผ๋“ค์„ Stream<T>๋กœ ๋ฌถ์–ด์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • ๋™์ผํ•œ ํƒ€์ž…์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•œ๋ฒˆ์— ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ›๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋ฉด ์œ ์šฉํ•˜๋‹ค.
// ํƒ€์ž…์„ <String>์œผ๋กœ ๋ช…์‹œํ•ด์•ผ ํ•จ
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
    scope.fork(() -> "A");
    scope.fork(() -> "B");
    
    Stream<String> results = scope.join(); // ["A", "B"] ์ŠคํŠธ๋ฆผ ๋ฐ˜ํ™˜
}
Java
  • anySuccessfulResultOrThrow()
    • ์ž‘์—… ์ค‘ ๊ฐ€์žฅ ๋จผ์ € ์„ฑ๊ณตํ•œ ์ž‘์—…์ด ๋‚˜์˜ค๋ฉด ๋‚˜๋จธ์ง€ ์ž‘์—…์€ ์ทจ์†Œํ•˜๊ณ  join() ํ˜ธ์ถœ์‹œ ์„ฑ๊ณตํ•œ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค.
    • ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์—ฌ๋Ÿฌ ์„œ๋ฒ„ ์ค‘ ๊ฐ€์žฅ ๋น ๋ฅธ ์‘๋‹ต์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹๋‹ค. (Redundancy)
try (var scope = StructuredTaskScope.open(Joiner.<String>anySuccessfulResultOrThrow())) {
    scope.fork(() -> slowApi());
    scope.fork(() -> fastApi());
    
    String winner = scope.join(); // "Fast Result" ๋ฐ˜ํ™˜
}
Java
  • awaitAll()
    • ๋๊นŒ์ง€ ๊ฐ„๋‹ค. ์„ฑ๊ณต์ด๋“  ์‹คํŒจ๋“  ์ƒ๊ด€์—†์ด ๋ชจ๋‘ ๋๋‚ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. join() ํ˜ธ์ถœ์‹œ ๊ฒฐ๊ณผ๋Š” ์—†๋‹ค.
    • ๋Œ€์‹œ๋ณด๋“œ ์ฒ˜๋Ÿผ ์ผ๋ถ€ ์œ„์ ฏ ํ•ญ๋ชฉ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๋‚˜๋จธ์ง€๋Š” ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹๊ฒ ๋‹ค.
try (var scope = StructuredTaskScope.open(Joiner.awaitAll())) {
    var taskA = scope.fork(() -> "Success");
    var taskB = scope.fork(() -> { throw new Exception("Fail"); });
    
    scope.join(); // ์˜ˆ์™ธ ์•ˆ ๋˜์ง. ๊ทธ๋ƒฅ ๊ธฐ๋‹ค๋ฆผ.
    
    // ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ƒํƒœ ํ™•์ธ
    if (taskA.state() == Subtask.State.SUCCESS) { ... }
}
Java

Structured Concurrency๋Š” ์•„์ง๊นŒ์ง€ Preview ๊ธฐ๋Šฅ์ด์ง€๋งŒ ์กฐ๋งŒ๊ฐ„ ์ •์‹ ๋ฒ„์ „์œผ๋กœ ์ถœ์‹œ๋˜์ง€ ์•Š์„๊นŒ ๊ธฐ๋Œ€ํ•œ๋‹ค.
Java 25์—์„œ ์ •์‹ ๋ฆด๋ฆฌ์ฆˆ๋œ Scoped Value์™€๋„ ์ฐฐ๋–ก๊ถํ•ฉ์œผ๋กœ ์ž˜ ๋งž๊ณ , Java 21์—์„œ ์ •์‹ ๋ฆด๋ฆฌ์ฆˆ ๋œ ๊ฐ€์ƒ์Šค๋ ˆ๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ๋„ ์ƒ๋‹นํžˆ ์œ ์šฉํ•  ๊ฒƒ์ด๋‹ค.