Skip to content

Latest commit

ย 

History

History
1003 lines (802 loc) ยท 48.2 KB

CompletableFuture.md

File metadata and controls

1003 lines (802 loc) ยท 48.2 KB

ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ ์™€ ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ ์€ ๋ณ‘๋ ฌ์„ฑ์˜ ๊ท€์ค‘ํ•œ ๋„๊ตฌ๋‹ค. ์ด๋“ค์€ ํ•œ ํƒœ์Šคํฌ๋ฅผ ์—ฌ๋Ÿฌ ํ•˜์œ„ ํƒœ์Šคํฌ๋กœ ๋‚˜๋ˆ„์–ด CPU์˜ ๋‹ค๋ฅธ ์ฝ”์–ด ๋˜๋Š” ๋‹ค๋ฅธ ๋จธ์‹ ์—์„œ ์ด๋“ค ํ•˜์œ„ ํƒœ์Šคํฌ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•œ๋‹ค.
๋ฐ˜๋ฉด ๋ณ‘๋ ฌ์„ฑ์ด ์•„๋‹ˆ๋ผ ๋™์‹œ์„ฑ์„ ํ•„์š”๋กœ ํ•˜๋Š” ์ƒํ™ฉ, ์ฆ‰ ์กฐ๊ธˆ์”ฉ ์—ฐ๊ด€๋œ ์ž‘์—…์„ ๊ฐ™์€ CPU์—์„œ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ƒ์‚ฐ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”์–ด๋ฅผ ๋ฐ”์˜๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๋ผ๋ฉด, ์›๊ฒฉ ์„œ๋น„์Šค๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กํ•จ์œผ๋กœ ์—ฐ์‚ฐ ์ž์›์„ ๋‚ญ๋น„ํ•˜๋Š” ์ผ์€ ํ”ผํ•ด์•ผ ํ•œ๋‹ค.

CompletableFuture์™€ java.util.concurrent.Flow์˜ ๊ถ๊ทน์ ์ธ ๋ชฉํ‘œ๋Š” ๊ฐ€๋Šฅํ•œํ•œ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋…๋ฆฝ์ ์ธ ํƒœ์Šคํฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด์„œ ๋ฉ€ํ‹ฐ์ฝ”์–ด ๋˜๋Š” ์—ฌ๋Ÿฌ ๊ธฐ๊ธฐ๋ฅผ ํ†ตํ•ด ์ œ๊ณต๋˜๋Š” ๋ณ‘๋ ฌ์„ฑ์„ ์‰ฝ๊ฒŒ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

sum = Arrays.stream(numbers).parallel().sum();

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

Executor์™€ ์Šค๋ ˆ๋“œ ํ’€

์ž๋ฐ” 5์—์„œ Executor ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์Šค๋ ˆ๋“œ ํ’€์„ ํ†ตํ•ด ์ž๋ฐ” ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ํƒœ์Šคํฌ ์ œ์ถœ๊ณผ ์‹คํ–‰์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ๊ณตํ–ˆ๋‹ค.

์Šค๋ ˆ๋“œ์˜ ๋ฌธ์ œ

์ž๋ฐ” ์Šค๋ ˆ๋“œ๋Š” ์ง์ ‘ ์šด์˜์ฒด์ œ ์Šค๋ ˆ๋“œ์— ์ ‘๊ทผํ•œ๋‹ค.
์šด์˜์ฒด์ œ ์Šค๋ ˆ๋“œ๋ฅผ ๋งŒ๋“ค๊ณ  ์ข…๋ฃŒํ•˜๋ ค๋ฉด ๋น„์‹ผ ๋น„์šฉ(ํŽ˜์ด์ง€ ํ…Œ์ด๋ธ”๊ณผ ๊ด€๋ จํ•œ ์ƒํ˜ธ์ž‘์šฉ)์„ ์น˜๋Ÿฌ์•ผ ํ•˜๋ฉฐ ๋”์šฑ์ด ์šด์˜์ฒด์ œ ์Šค๋ ˆ๋“œ์˜ ์ˆซ์ž๋Š” ์ œํ•œ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ด ๋ฌธ์ œ๋‹ค.
์šด์˜์ฒด์ œ๊ฐ€ ์ง€์›ํ•˜๋Š” ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ์ดˆ๊ณผํ•ด ์‚ฌ์šฉํ•˜๋ฉด ์ž๋ฐ” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฐฉ์‹์œผ๋กœ ํฌ๋ž˜์‹œ ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๊ธฐ์กด ์Šค๋ ˆ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ƒํƒœ์—์„œ ๊ณ„์† ์ƒˆ๋กœ์šด ์Šค๋ ˆ๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ์ƒํ™ฉ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

๋ณดํ†ต ์šด์˜์ฒด์ œ์™€ ์ž๋ฐ”์˜ ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜๊ฐ€ ํ•˜๋“œ์›จ์–ด ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜๋ณด๋‹ค ๋งŽ์œผ๋ฏ€๋กœ ์ผ๋ถ€ ์šด์˜์ฒด์ œ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ธ”๋ก๋˜๊ฑฐ๋‚˜ ์ž๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์—์„œ ๋ชจ๋“  ํ•˜๋“œ์›จ์–ด ์Šค๋ ˆ๋“œ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ• ๋‹น๋œ ์ƒํ™ฉ์— ๋†“์„ ์ˆ˜ ์žˆ๋‹ค.
๋งŒ์•ฝ 8๊ฐœ์˜ ์ฝ”์–ด๋ฅผ ๊ฐ€์ง€๋ฉฐ ๊ฐ ์ฝ”์–ด๋Š” ๋‘ ๊ฐœ์˜ ๋Œ€์นญ ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ(symmetric multiprocessing) ํ•˜๋“œ์›จ์–ด ์Šค๋ ˆ๋“œ๋ฅผ ํฌํ•จํ•˜๋ฏ€๋กœ ํ•˜๋“œ์›จ์–ด ์Šค๋ ˆ๋“œ๋ฅผ ์ด 16๊ฐœ ํฌํ•จํ•˜๋Š”๋ฐ ์„œ๋ฒ„์—๋Š” ํ”„๋กœ์„ธ์„œ๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ํฌํ•จํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ•˜๋“œ์›จ์–ด ์Šค๋ ˆ๋“œ 64๊ฐœ๋ฅผ ๋ณด์œ ํ•  ์ˆ˜ ์žˆ๋‹ค.
ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉํ•  ์ตœ์ ์˜ ์ž๋ฐ” ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•˜๋“œ์›จ์–ด์˜ ์ฝ”์–ด ๊ฐœ์ˆ˜์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง„๋‹ค.

์Šค๋ ˆ๋“œ ํ’€ ์žฅ์ 

์ž๋ฐ” ExecutorService๋Š” ํƒœ์Šคํฌ๋ฅผ ์ œ์ถœํ•˜๊ณ  ๋‚˜์ค‘์— ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
Executors.newFixedThreadPool ๊ฐ™์€ ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ด์šฉํ•ด ์Šค๋ ˆ๋“œ ํ’€์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šค๋ ˆ๋“œ ํ’€์—์„œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์Šค๋ ˆ๋“œ๋กœ ์ œ์ถœ๋œ ํƒœ์Šคํฌ๋ฅผ ๋จผ์ € ์˜จ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•œ๋‹ค.
์ด๋“ค ํƒœ์Šคํฌ ์‹คํ–‰์ด ์ข…๋ฃŒ๋˜๋ฉด ์ด๋“ค ์Šค๋ ˆ๋“œ๋ฅผ ํ’€๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
ํ•˜๋“œ์›จ์–ด์— ๋งž๋Š” ์ˆ˜์˜ ํƒœ์Šคํฌ๋ฅผ ์œ ์ง€ํ•จ๊ณผ ๋™์‹œ์— ์ˆ˜ ์ฒœ๊ฐœ์˜ ํƒœ์Šคํฌ๋ฅผ ์Šค๋ ˆ๋“œ ํ’€์— ์•„๋ฌด ์˜ค๋ฒ„ํ—ค๋“œ ์—†์ด ์ œ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค.
ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ํƒœ์Šคํฌ(Runnable์ด๋‚˜ Callable)๋ฅผ ์ œ๊ณตํ•˜๋ฉด ์Šค๋ ˆ๋“œ๊ฐ€ ์ด๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

์Šค๋ ˆ๋“œ ํ’€ ๋‹จ์ 

์Šค๋ ˆ๋“œ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์Šค๋ ˆ๋“œ ํ’€์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•˜์ง€๋งŒ ๋‘ ๊ฐ€์ง€ ์‚ฌํ•ญ์„ ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

  1. k์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ€์ง„ ์Šค๋ ˆ๋“œ ํ’€์€ ์˜ค์ง k๋งŒํผ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ดˆ๊ณผ๋กœ ์ œ์ถœ๋œ ํƒœ์Šคํฌ๋Š” ํ์— ์ €์žฅ๋˜๋ฉฐ ์ด์ „์— ํƒœ์Šคํฌ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ข…๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์Šค๋ ˆ๋“œ์— ํ• ๋‹นํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŽ์€ ์Šค๋ ˆ๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ์ผ์„ ํ”ผํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ณดํ†ต ์ด ์ƒํ™ฉ์€ ์•„๋ฌด ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์ง€๋งŒ sleep ์ƒํƒœ๊ฑฐ๋‚˜ I/O๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ฑฐ๋‚˜ ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ํƒœ์Šคํฌ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.
    • ์Šค๋ ˆ๋“œ ๋ธ”๋ก ์ƒํ™ฉ์—์„œ๋Š” ํƒœ์Šคํฌ๊ฐ€ ์›Œ์ปค ์Šค๋ ˆ๋“œ์— ํ• ๋‹น๋œ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜์ง€๋งŒ ์•„๋ฌด ์ž‘์—…๋„ ํ•˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.
    • ์œ„์˜ ๊ทธ๋ฆผ์„ ๋ณด๋ฉด 4๊ฐœ์˜ ํ•˜๋“œ์›จ์–ด ์Šค๋ ˆ๋“œ์™€ 5๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ–๋Š” ์Šค๋ ˆ๋“œ ํ’€์— 20๊ฐœ์˜ ํƒœ์Šคํฌ๋ฅผ ์ œ์ถœํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.
    • ๋ชจ๋“  ํƒœ์Šคํฌ๊ฐ€ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋˜๋ฉด์„œ 20๊ฐœ์˜ ํƒœ์Šคํฌ๋ฅผ ์‹คํ–‰ํ•  ๊ฒƒ์ด๋ผ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฒ˜์Œ ์ œ์ถœํ•œ ์„ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž ์„ ์ž๊ฑฐ๋‚˜ I/O๋ฅผ ๊ธฐ๋‹ค๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.
    • ๊ทธ๋Ÿฌ๋ฉด ๋‚˜๋จธ์ง€ 15๊ฐœ์˜ ํƒœ์Šคํฌ๋ฅผ ๋‘ ์Šค๋ ˆ๋“œ๊ฐ€ ์‹คํ–‰ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ž‘์—… ํšจ์œจ์„ฑ์ด ์˜ˆ์ƒ๋ณด๋‹ค ์ ˆ๋ฐ˜์œผ๋กœ ๋–จ์–ด์ง„๋‹ค.
    • ์ฒ˜์Œ ์ œ์ถœํ•œ ํƒœ์Šคํฌ๊ฐ€ ๊ธฐ์กด ์‹คํ–‰ ์ค‘์ธ ํƒœ์Šคํฌ๊ฐ€ ๋‚˜์ค‘์˜ ํƒœ์Šคํฌ ์ œ์ถœ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š ์ƒํ™ฉ(Future์˜ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด)์ด๋ผ๋ฉด ๋ฐ๋“œ๋ฝ์— ๊ฑธ๋ฆด ์ˆ˜๋„ ์žˆ๋‹ค.
    • ํ•ต์‹ฌ์€ ๋ธ”๋ก(์ž๊ฑฐ๋‚˜ ์ด๋ฒคํŠธ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š”)ํ•  ์ˆ˜ ์žˆ๋Š” ํƒœ์Šคํฌ๋Š” ์Šค๋ ˆ๋“œ ํ’€์— ์ œ์ถœํ•˜์ง€ ๋ง์•„์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด์ง€๋งŒ ํ•ญ์ƒ ์ด๋ฅผ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.
  2. ์ค‘์š”ํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ์ฃฝ๋Š” ์ผ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๋ณดํ†ต ์ž๋ฐ” ํ”„๋กœ๊ทธ๋žจ์€ main์ด ๋ฐ˜ํ™˜๋˜๊ธฐ ์ „์— ๋ชจ๋“  ์Šค๋ ˆ๋“œ์˜ ์ž‘์—…์ด ๋๋‚˜๊ธธ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.
    • ์Šค๋ ˆ๋“œ ํ’€์˜ ์›Œ์ปค ์Šค๋ ˆ๋“œ๊ฐ€ ๋งŒ๋“ค์–ด์ง„ ๋‹ค์Œ ๋‹ค๋ฅธ ํƒœ์Šคํฌ ์ œ์ถœ์„ ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ ์ข…๋ฃŒ๋˜์ง€ ์•Š์€ ์ƒํƒœ์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์Šค๋ ˆ๋“œ ํ’€์„ ์ข…๋ฃŒํ•˜๋Š” ์Šต๊ด€์„ ๊ฐ–๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.
    • ๋ณดํ†ต ์žฅ๊ธฐ๊ฐ„ ์‹คํ–‰ํ•˜๋Š” ์ธํ„ฐ๋„ท ์„œ๋น„์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๋„๋ก ์˜ค๋ž˜ ์‹คํ–‰๋˜๋Š” ExecutorService๋ฅผ ๊ฐ–๋Š” ๊ฒƒ์€ ํ”ํ•œ ์ผ์ด๋‹ค.
    • ์ž๋ฐ”๋Š” ์ด๋Ÿฐ ์ƒํ™ฉ์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋„๋ก Thread.setDaemon๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

์—„๊ฒฉํ•œ ํฌํฌ/์กฐ์ธ

ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋งํ•˜๋Š” ๋™์‹œ์„ฑ์€ ํ•œ ๊ฐœ์˜ ํŠน๋ณ„ํ•œ ์†์„ฑ
์ฆ‰, ํƒœ์Šคํฌ๋‚˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์•ˆ์—์„œ ์‹œ์ž‘๋˜๋ฉด ๊ทธ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์€ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ  ์ž‘์—…์ด ๋๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ ธ๋‹ค.
๋‹ค์‹œ ๋งํ•ด ์Šค๋ ˆ๋“œ ์ƒ์„ฑ๊ณผ join()์ด ํ•œ ์Œ์ฒ˜๋Ÿผ ์ค‘์ฒฉ๋œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๋‚ด์— ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.
์ด๋ฆ„ ์—„๊ฒฉํ•œ ํฌํฌ/์กฐ์ธ ์ด๋ผ๊ณ  ํ•œ๋‹ค.

์—„๊ฒฉํ•œ ํฌํฌ/์กฐ์ธ, ํ™”์‚ดํ‘œ๋Š” ์Šค๋ ˆ๋“œ, ์›์€ ํฌํฌ์™€ ์กฐ์ธ์„, ์‚ฌ๊ฐํ˜•์€ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ๊ณผ ๋ฐ˜ํ™˜์„ ์˜๋ฏธํ•œ๋‹ค.

์—ฌ์œ ๋กœ์šด ํฌํฌ/์กฐ์ธ

์—„๊ฒฉํ•œ๊ณผ ์—ฌ์œ ๋กœ์šด์˜ ์ฐจ์ด๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์–ด๋””์—์„œ ํฌํฌ๋ฅผ ํ•˜๋Š”์ง€์˜ ์ฐจ์ด์ธ ๊ฒƒ ๊ฐ™๋‹ค.
์—„๊ฒฉํ•œ์€ ํฌํฌ๋ฅผ ๋œฌ ์ดํ›„์— ์ž์‹ ์Šค๋ ˆ๋“œ์˜ ๋กœ์ง์ด ์‹คํ–‰๋˜๋„๋ก ๋˜์–ด์žˆ๊ณ , ์—ฌ์œ ๋กœ์šด์€ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ํฌํฌ๋ฅผ ๋œจ๋Š” ์ฐจ์ด์ธ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

์•„๋ž˜์™€ ๊ฐ™์ด๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ž์— ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋„๋ก ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋œ ํ›„์—๋„ ๋งŒ๋“ค์–ด์ง„ ํƒœ์Šคํฌ ์‹คํ–‰์ด ๊ณ„์†๋˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ๋ผ ํ•œ๋‹ค.

๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์œ„ํ—˜์„ฑ

  1. ์Šค๋ ˆ๋“œ ์‹คํ–‰์€ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ ๋‹ค์Œ์˜ ์ฝ”๋“œ์™€ ๋™์‹œ์— ์‹คํ–‰๋˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๊ฒฝ์Ÿ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.
  2. ๊ธฐ์กด ์‹คํ–‰ ์ค‘์ด๋˜ ์Šค๋ ˆ๋“œ๊ฐ€ ์ข…๋ฃŒ๋˜์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ ์ž๋ฐ”์˜ main()๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๊ธดํ•˜์ง€๋งŒ ์–ด๋Š ๋ฐฉ๋ฒ•๋„ ์•ˆ์ „ํ•˜์ง€ ์•Š๋‹ค.
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ข…๋ฃŒํ•˜์ง€ ๋ชปํ•˜๊ณ  ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ์‹คํ–‰์„ ๋๋‚ผ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ข…๋ฃŒ๋ฅผ ๋ฐฉํ•ดํ•˜๋Š” ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ•์ œ์ข…๋ฃŒ ์‹œํ‚ค๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ข…๋ฃŒํ•œ๋‹ค.
    • main()๋ฉ”์„œ๋“œ๋Š” ๋ชจ๋“  ๋น„๋ฐ๋ชฌ ์Šค๋ ˆ๋“œ๊ฐ€ ์ข…๋ฃŒ๋  ๋•Œ ๊นŒ์ง€ ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

์ž ์ž๊ธฐ(๊ทธ๋ฆฌ๊ณ  ๊ธฐํƒ€ ๋ธ”๋กœํ‚น ๋™์ž‘)๋Š” ํ•ด๋กœ์šด ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ

์Šค๋ ˆ๋“œ๋ฅผ sleep()ํ•˜์—ฌ๋„ ์—ฌ์ „ํžˆ ์‹œ์Šคํ…œ ์ž์›์„ ์ ์œ ํ•˜๊ณ  ์žˆ๋‹ค.
์Šค๋ ˆ๋“œ ํ’€์—์„œ ์ž ์„ ์ž๋Š” ํƒœ์Šคํฌ๋Š” ๋‹ค๋ฅธ ํƒœ์Šคํฌ๊ฐ€ ์‹œ์ž‘๋˜์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰์œผ๋ฏ€๋กœ ์ž์›์„ ์†Œ๋น„ํ•˜๋Š” ์‚ฌ์‹ค์„ ๋ช…์‹ฌํ•ด์•ผ ํ•œ๋‹ค.
๋ชจ๋“  ๋ธ”๋ก ๋™์ž‘๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋‹ค. ๋ธ”๋ก ๋™์ž‘์€ ๋‹ค๋ฅธ ํƒœ์Šคํฌ๊ฐ€ ์–ด๋–ค ๋™์ž‘์„ ์™„๋ฃŒํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์ž‘(์˜ˆ๋ฅผ ๋“ค์–ด, Future์— get() ํ˜ธ์ถœ)๊ณผ ์™ธ๋ถ€ ์ƒํ˜ธ์ž‘์šฉ(์˜ˆ๋ฅผ ๋“ค์–ด, ๋””๋น„ ์ž‘์—…์ด๋‚˜ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋“ฑ)์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์ž‘ ๋‘ ๊ฐ€์ง€๋กœ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

// A์ฝ”๋“œ
void scheduling() throws InterruptedException() {
    doSomething("first");
    Thread.sleep(5000);
    doSomething("second");
}

// B์ฝ”๋“œ
void scheduling() {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    doSomething("first");
    executorService.schedule(() -> doSomething("second"), 5, SECONDS);
    executorService.shutdown();
}

์œ„์˜ ์ฝ”๋“œ๋Š” ์Šค๋ ˆ๋“œ ํ’€ ํ์— ์ถ”๊ฐ€๋˜๋ฉฐ ๋‚˜์ค‘์— ์ฐจ๋ก€๊ฐ€ ๋˜๋ฉด ์‹คํ–‰๋œ๋‹ค.
ํ•˜์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋ฉด ์›Œ์ปค ์Šค๋ ˆ๋“œ๋ฅผ ์ ์œ ํ•œ ์ƒํƒœ์—์„œ ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๊ณ  5์ดˆ๋ฅผ ์ž”๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๊นจ์–ด๋‚˜์„œ ๋‘ ๋ฒˆ์งธ ์ž‘์—…์„ ์‹คํ–‰ํ•œ ๋‹ค์Œ ์ž‘์—…์„ ์ข…๋ฃŒํ•˜๊ณ  ์›Œ์ปค ์Šค๋ ˆ๋“œ๋ฅผ ํ•ด์ œํ•œ๋‹ค.

๋‘ ๊ฐœ์˜ ์ฝ”๋“œ๋Š” ๋˜‘๊ฐ™์€ ํ–‰๋™์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ B์ฝ”๋“œ๊ฐ€ ๋” ์ข‹์€ ์ด์œ ๋Š” A๊ฐ€ ์ž๋Š” ๋™์•ˆ ์Šค๋ ˆ๋“œ ์ž์›์„ ์ ์œ ํ•˜๋Š” ๋ฐ˜๋ฉด B๋Š” ๋‹ค๋ฅธ ์ž‘์—…์ด ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.
(์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์ด ๋ฉ”๋ชจ๋ฆฌ๋งŒ ์กฐ๊ธˆ ๋” ์‚ฌ์šฉํ–ˆ๋‹ค.)

ํƒœ์Šคํฌ๋ฅผ ๋งŒ๋“ค ๋•Œ๋Š” ์ด๋Ÿฐ ํŠน์ง•์„ ์ž˜ ํ™œ์šฉํ•ด์•ผ ํ•œ๋‹ค.
ํƒœ์Šคํฌ๊ฐ€ ์‹คํ–‰๋˜๋ฉด ๊ท€์ค‘ํ•œ ์ž์›์„ ์ ์œ ํ•˜๋ฏ€๋กœ ํƒœ์Šคํฌ๊ฐ€ ๋๋‚˜์„œ ์ž์›์„ ํ•ด์ œํ•˜๊ธฐ ์ „๊นŒ์ง€ ํƒœ์Šคํฌ๋ฅผ ๊ณ„์† ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค.
ํƒœ์Šคํฌ๋ฅผ ๋ธ”๋กํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค๋Š” ๋‹ค์Œ ์ž‘์—…์„ ํƒœ์Šคํฌ๋กœ ์ œ์ถœํ•˜๊ณ  ํ˜„์žฌ ํƒœ์Šคํฌ๋Š” ์ข…๋ฃŒํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•˜๋‹ค.

๋Œ€ํ‘œ์ ์œผ๋กœ I/O ์ž‘์—…์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
๊ณ ์ „์ ์œผ๋กœ ์ฝ๊ธฐ ์ž‘์—…์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ธ”๋กํ•˜์ง€ ์•Š๋Š” '์ฝ๊ธฐ ์‹œ์ž‘' ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ฝ๊ธฐ ์ž‘์—…์ด ๋๋‚˜๋ฉด ์ด๋ฅผ ์ฒ˜๋ฆฌํ•  ๋‹ค์Œ ํƒœ์Šคํฌ๋ฅผ ๋Ÿฐํƒ€์ž„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์Šค์ผ€์ค„ํ•˜๋„๋ก ์š”์ฒญํ•˜๊ณ  ์ข…๋ฃŒํ•œ๋‹ค.

์Šค๋ ˆ๋“œ์—๋Š” ์ œํ•œ์ด ์žˆ๊ณ  ์ €๋ ดํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ž ์„ ์ž๊ฑฐ๋‚˜ ๋ธ”๋กํ•ด์•ผ ํ•˜๋Š” ์—ฌ๋Ÿฌ ํƒœ์Šคํฌ๊ฐ€ ์žˆ์„ ๋•Œ ๊ฐ€๋Šฅํ•˜๋ฉด B์ฝ”๋“œ์˜ ํ˜•์‹์„ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
CompletableFuture ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์ด์ „์— ์‚ดํŽด๋ณธ Future์— get()์„ ์ด์šฉํ•ด ๋ช…์‹œ์ ์œผ๋กœ ๋ธ”๋กํ•˜์ง€ ์•Š๊ณ  ์ฝค๋น„๋„ค์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ ์ด๋Ÿฐ ํ˜•์‹์˜ ์ฝ”๋“œ๋ฅผ ๋Ÿฐํƒ€์ž„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด์— ์ถ”๊ฐ€ํ•œ๋‹ค.

๋น„๋™๊ธฐ API์—์„œ ์˜ˆ์™ธ๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ๊นŒ?

Future๋‚˜ ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ˜•์‹์˜ ๋น„๋™๊ธฐ API์—์„œ ํ˜ธ์ถœ๋œ ๋ฉ”์„œ๋“œ์˜ ์‹ค์ œ ๋ฐ”๋””๋Š” ๋ณ„๋„์˜ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ๋˜๋ฉฐ ์ด๋•Œ ๋ฐœ์ƒํ•˜๋Š” ์–ด๋–ค ์—๋Ÿฌ๋Š” ์ด๋ฏธ ํ˜ธ์ถœ์ž์˜ ์‹คํ–‰ ๋ฒ”์œ„์™€๋Š” ๊ด€๊ณ„๊ฐ€ ์—†๋Š” ์ƒํ™ฉ์ด ๋œ๋‹ค.
Future๋ฅผ ๊ตฌํ˜„ํ•œ CompletableFuture์—์„œ๋Š” ๋Ÿฐํƒ€์ž„ get()๋ฉ”์„œ๋“œ์— ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฉฐ ์˜ˆ์™ธ์—์„œ ํšŒ๋ณตํ•  ์ˆ˜ ์žˆ๋„๋ก exceptionally()๊ฐ™์€ ๋ฉ”์„œ๋“œ๋„ ์ œ๊ณตํ•œ๋‹ค.

๋ฆฌ์•กํ‹ฐ๋ธŒ ํ˜•์‹์˜ ๋น„๋™๊ธฐ API์—์„œ๋Š” return ๋Œ€์‹  ๊ธฐ์กด ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋˜๋ฏ€๋กœ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์•„๋ž˜์™€ ๊ฐ™์ด ์‹คํ–‰๋  ์ถ”๊ฐ€ ์ฝœ๋ฐฑ์„ ๋งŒ๋“ค์–ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค.

void testFunc(int x, Consumer<Integer> dealWithResult, Consumer<Throwable> dealWithException);

Flow API์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฝœ๋ฐฑ์„ ํ•œ ๊ฐ์ฒด(๋„ค ๊ฐœ์˜ ์ฝœ๋ฐฑ์„ ๊ฐ๊ฐ ๋Œ€ํ‘œํ•˜๋Š” ๋„ค ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” Subscriber<T>)๋กœ ๊ฐ์‹ผ๋‹ค.

public static interface Subscriber<T> {
    public void onSubscribe(Subscription subscription);
    /**
     * ๊ฐ’์ด ์žˆ์„ ๋•Œ
     */
    public void onNext(T item);
    /**
     * ๋„์ค‘์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ
     */
    public void onError(Throwable throwable);
    /**
     * ๊ฐ’์„ ๋‹ค ์†Œ์ง„ํ–ˆ๊ฑฐ๋‚˜ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ๋” ์ด์ƒ ์ฒ˜๋ฆฌํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋•Œ
     */
    public void onComplete();
}

์œ„์™€ ๊ฐ™์€ ์ข…๋ฅ˜์˜ ํ˜ธ์ถœ์„ ๋ฉ”์‹œ์ง€ ๋˜๋Š” ์ด๋ฒคํŠธ ๋ผ ๋ถ€๋ฅธ๋‹ค.

CompletableFuture์™€ ์ฝค๋น„๋„ค์ดํ„ฐ๋ฅผ ์ด์šฉํ•œ ๋™์‹œ์„ฑ

int t = p(x);
Future<Integer> a1 = executorService.submit(() -> q1(t));
Future<Integer> a2 = executorService.submit(() -> q2(t));
Future<Integer> a3 = executorService.submit(() -> q3(t));
System.out.println( r(a1.get(),a2.get() + a3.get()));

์œ„์˜ ์ฝ”๋“œ์—์„œ ๋ณ‘๋ ฌ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜๋ ค๋ฉด ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ Future๋กœ ๊ฐ์‹ธ์•ผ ํ•œ๋‹ค.
์ด๋ ‡๊ฒŒ ๋งŽ์€ ํƒœ์Šคํฌ๊ฐ€ get() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด Future๊ฐ€ ๋๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํƒœ์— ๋†“์ด๊ฒŒ ๋˜๋ฉด ํ•˜๋“œ์›จ์–ด์˜ ๋ณ‘๋ ฌ์„ฑ์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜ ์‹ฌ์ง€์–ด ๋ฐ๋“œ๋ฝ์— ๊ฑธ๋ฆด ์ˆ˜๋„ ์žˆ๋‹ค.

CompletableFuture ์™€ ์ฝค๋น„๋„ค์ดํ„ฐ ๋ฅผ ํ™œ์šฉํ•ด ํƒœ์Šคํฌ๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋งŒ๋“œ๋Š” ์ผ์„ ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค.
(๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ž๋ฐ” 8 ์ŠคํŠธ๋ฆผ์€ ์ž๋ฃŒ ๊ตฌ์กฐ๋ฅผ ๋ฐ˜๋ณตํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ŠคํŠธ๋ฆผ ์ฝค๋น„๋„ค์ดํ„ฐ๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค.)

์ž๋ฐ” 8์—์„œ Future ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ธ CompletableFuture๋ฅผ ์ด์šฉํ•ด Future๋ฅผ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋๋‹ค.
๊ทธ๋Ÿผ ComposableFuture์ด ์•„๋‹ˆ๋ผ CompletableFuture๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ์ด์œ ๋Š” ๋ญ˜๊นŒ?
์ผ๋ฐ˜์ ์œผ๋กœ Future๋Š” ์‹คํ–‰ํ•ด์„œ get()์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋Š” Callable๋กœ ๋งŒ๋“ค์–ด์ง„๋‹ค.
ํ•˜์ง€๋งŒ CompletableFuture๋Š” ์‹คํ–‰ํ•  ์ฝ”๋“œ ์—†์ด Future๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๋ฉฐ complete() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๋‚˜์ค‘์— ์–ด๋–ค ๊ฐ’์„ ์ด์šฉํ•ด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ด๋ฅผ ์™„๋ฃŒํ•  ์ˆ˜ ์žˆ๊ณ  get()์œผ๋กœ ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— CompletableFuture๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

@Test
@DisplayName("CompletableFuture ์ ์šฉ")
void completableFutureCombine() throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    Future<Integer> result1 = executorService.submit(() -> sum(100));
    Future<Integer> result2 = executorService.submit(() -> sum(1000));

    Integer sum = Integer.sum(result1.get(), result2.get());
    Assertions.assertThat(sum).isEqualTo(505550);

    CompletableFuture<Integer> completableFuture1 = new CompletableFuture<>();
    CompletableFuture<Integer> completableFuture2 = new CompletableFuture<>();
    CompletableFuture<Integer> completableFuture3 = completableFuture1.thenCombine(completableFuture2, Integer::sum);
    executorService.submit(() -> completableFuture1.complete(sum(100)));
    executorService.submit(() -> completableFuture2.complete(sum(1000)));

    Assertions.assertThat(completableFuture3.get()).isEqualTo(505550);
    executorService.shutdown();
}

thenCombine()์„ ํ†ตํ•ด ๋‘ ์—ฐ์‚ฐ์ด ๋๋‚ฌ์„ ๋•Œ ์Šค๋ ˆ๋“œ ํ’€์—์„œ ์‹คํ–‰๋  ์—ฐ์‚ฐ์„ ๋งŒ๋“ ๋‹ค.
๊ฒฐ๊ณผ๋ฅผ ๋” ํ•˜๋Š” ์—ฐ์‚ฐ์€ ์•ž์˜ ๋‘ ์ž‘์—…์ด ๋๋‚˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์‹คํ–‰๋˜์ง€ ์•Š์•„ ๋จผ์ € ์‹œ์ž‘ํ•ด์„œ ๋ธ”๋ก๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ํŠน์ง•์ด๋‹ค.
์ƒํ™ฉ์— ๋”ฐ๋ผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ Future์˜ get()์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํ™ฉ์ด ํฐ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„ ๋•Œ๋Š” Future๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ด๋„ ๊ดœ์ฐฎ๋‹ค.
ํ•˜์ง€๋งŒ ์—ฌ๋Ÿฌ ๊ฐœ์˜ Future๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ณ‘๋ ฌ ์‹คํ–‰์˜ ํšจ์œจ์„ฑ์„ ๋†’์ด๊ณ  ๋ฐ๋“œ๋ฝ์€ ํ”ผํ•  ์ˆ˜ ์žˆ๋Š” ์ฝค๋น„๋„ค์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์ž.

๋ฐœํ–‰-๊ตฌ๋… ๊ทธ๋ฆฌ๊ณ  ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ

์ž๋ฐ” 9์—์„œ๋Š” Flow์˜ ์ธํ„ฐํŽ˜์ด์Šค์— ๋ฐœํ–‰-๊ตฌ๋… ๋ชจ๋ธ ์„ ์ ์šฉํ•ด ๋ฆฌ์•กํ‹ฐ๋ธŒ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ œ๊ณตํ•œ๋‹ค.

  1. ๊ตฌ๋…์ž ๊ฐ€ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐœํ–‰์ž
  2. ์ด ์—ฐ๊ฒฐ์„ ๊ตฌ๋… ์ด๋ผ ํ•œ๋‹ค.
  3. ์ด ์—ฐ๊ฒฐ์„ ์ด์šฉํ•ด ๋ฉ”์‹œ์ง€ (๋˜๋Š” ์ด๋ฒคํŠธ)๋ฅผ ์ „์†กํ•œ๋‹ค.

์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•œ ๊ตฌ๋…์ž๋กœ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๊ณ  ํ•œ ์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๋ณ„ ์ŠคํŠธ๋ฆผ์„ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ•œ ์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ ๊ตฌ๋…์ž์— ๊ฐ€์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‘ ์ •๋ณด๋ฅผ ํ•ฉ์น˜๋Š” ์˜ˆ์ œ

๋‘ ์ •๋ณด ์†Œ์Šค๋กœ ๋ถ€ํ„ฐ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ํ•ฉ์ณ์„œ ๋‹ค๋ฅธ ๊ตฌ๋…์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋„๋ก ๋ฐœํ–‰ํ•˜๋Š” ์˜ˆ๋ฅผ ํ†ตํ•ด ๋ฐœํ–‰-๊ตฌ๋…์˜ ํŠน์ง•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
=C1+C2๋ผ๋Š” ๊ณต์‹์„ ํฌํ•จํ•˜๋Š” ์Šคํ”„๋ ˆ๋“œ์‹œํŠธ ์…€ C3์„ ๋งŒ๋“ ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.
C1์ด๋‚˜ C2 ์…€์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋์„ ๋•Œ C3์—๊ฒŒ ๋‘ ๊ฐ’์„ ๋”ํ•˜๋„๋ก C1๊ณผ C2์— ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ C3์„ ๊ตฌ๋…ํ•˜๋„๋ก ๋งŒ๋“ค์–ด๋ณด์ž.

interface Publisher<T> {
    void subscribe(Subscriber<? super T> subscriber);
}

interface Subscriber<T> {
    void onNext(T t);
}
private class SimpleCell implements Publisher<Integer>, Subscriber<Integer> {
    private int value = 0;
    private String name;
    private List<Subscriber> subscribers = new ArrayList<>();

    public SimpleCell(String name) {
        this.name = name;
    }

    public int getValue() {
        return this.value;
    }

    private void notifyAllSubscribers() {
        this.subscribers.forEach(subscriber -> subscriber.onNext(this.value));
    }

    @Override
    public void subscribe(Subscriber<? super Integer> subscriber) {
        this.subscribers.add(subscriber);
    }

    @Override
    public void onNext(Integer integer) {
        this.value = integer;
        System.out.printf("%s : %d\n", this.name, this.value);
        notifyAllSubscribers();
    }
}

private class ArithmeticCell extends SimpleCell {
    private int left;
    private int right;

    public ArithmeticCell(String name) {
        super(name);
    }

    public void setLeft(int left) {
        this.left = left;
        super.onNext(left + this.right);
    }

    public void setRight(int right) {
        this.right = right;
        super.onNext(right + this.left);
    }
}

@Test
@DisplayName("์…€ pub-sub")
void pubsub() {
    SimpleCell c1 = new SimpleCell("C1");
    SimpleCell c2 = new SimpleCell("C2");
    SimpleCell c3 = new SimpleCell("C3");

    c1.subscribe(c3);
    c2.subscribe(c3);
    c1.onNext(10);
    Assertions.assertThat(c3.value).isEqualTo(10);
    c2.onNext(20);
    Assertions.assertThat(c3.value).isEqualTo(20);

    ArithmeticCell c4 = new ArithmeticCell("C4");
    c1.subscribe(c4::setLeft);
    c2.subscribe(c4::setRight);

    c1.onNext(10);
    Assertions.assertThat(c4.getValue()).isEqualTo(10);
    c2.onNext(20);
    Assertions.assertThat(c4.getValue()).isEqualTo(30);

//        C1 : 10
//        C3 : 10
//        C2 : 20
//        C3 : 20
//        C1 : 10
//        C3 : 10
//        C4 : 10
//        C2 : 20
//        C3 : 20
//        C4 : 30
}

๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐœํ–‰์ž(์ƒ์‚ฐ์ž)์—์„œ ๊ตฌ๋…์ž(์†Œ๋น„์ž)๋กœ ํ๋ฆ„์— ์ฐฉ์•ˆํ•ด ๊ฐœ๋ฐœ์ž๋Š” ์ด๋ฅผ ์—…์ŠคํŠธ๋ฆผ ๋˜๋Š” ๋‹ค์šด์ŠคํŠธ๋ฆผ ์ด๋ผ ๋ถ€๋ฅธ๋‹ค.
์œ„ ์˜ˆ์ œ์—์„œ ๋ฐ์ดํ„ฐ value๋Š” ์—…์ŠคํŠธ๋ฆผ onNext() ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌ๋˜๊ณ , notifyAllSubscribers() ํ˜ธ์ถœ์„ ํ†ตํ•ด ๋‹ค์šด์ŠคํŠธ๋ฆผ onNext() ํ˜ธ์ถœ๋กœ ์ „๋‹ฌ๋œ๋‹ค.

์—ฌ๊ธฐ์—์„œ๋Š” ์„ค๋ช…ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ onError์™€ onComplete์ด Flow API์˜ Subscriber์—์„œ ์ง€์›๋˜๋‹ˆ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋“ค์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ํ๋ฆ„์„ ๋” ์ž์„ธํ•˜๊ฒŒ ์ œ์–ดํ•ด์•ผ ํ•œ๋‹ค.

์••๋ ฅ๊ณผ ์—ญ์••๋ ฅ

์œ„์™€ ๊ฐ™์€ ๋ฐœํ–‰-๊ตฌ๋… ํŒจํ„ด์—์„œ ์—„์ฒญ๋‚œ ๋ฐ์ดํ„ฐ๊ฐ€ onNext()๋กœ ์ „๋‹ฌ๋œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? ์ด๋Ÿฐ ์ƒํ™ฉ์„ ์••๋ ฅ ์ด๋ผ ๋ถ€๋ฅธ๋‹ค.
์ˆ˜์ง ํŒŒ์ดํ”„์— ๋งŽ์€ ๋ฐ์ดํ„ฐ์˜ ์••๋ ฅ์ด ๋“ค์–ด์˜จ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์„ ๋•Œ ์ด ์••๋ ฅ์„ ์กฐ์ ˆํ•˜๋Š” ์—ญ์••๋ ฅ ๊ธฐ๋ฒ•์ด ํ•„์š”ํ•˜๋‹ค.
๋ฐœํ–‰์ž๊ฐ€ ๋ฌดํ•œ์˜ ์†๋„๋กœ ์•„์ดํ…œ์„ ๋ฐฉ์ถœํ•˜๋Š” ๋Œ€์‹  ์š”์ฒญํ–ˆ์„ ๋•Œ๋งŒ ๋‹ค์Œ ์•„์ดํ…œ์„ ๋ณด๋‚ด๋„๋กํ•˜๋Š” request() ๋ฉ”์„œ๋“œ (Subscription์ด๋ผ๋Š” ์ƒˆ ์ธํ„ฐํŽ˜์ด์Šค์— ํฌํ•จ)๋ฅผ ์ œ๊ณตํ•œ๋‹ค. (๋ฐ€์–ด๋‚ด๊ธฐ ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ ๋‹น๊น€ ๋ชจ๋ธ)

Publisher๋Š” ์—ฌ๋Ÿฌ Subscriber๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์—ญ์••๋ ฅ ์š”์ฒญ์ด ํ•œ ์—ฐ๊ฒฐ์—๋งŒ ์˜ํ–ฅ์„ ๋ฏธ์ณ์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

/**
 * ์ง€์ •๋œ ๊ตฌ๋…์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ๊ตฌ๋…์ž ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค. 
 * ์ด ๋ฉ”์„œ๋“œ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ฒฐ๊ณผ ๋™์ž‘์ด ๋ณด์žฅ๋˜์ง€ ์•Š์ง€๋งŒ ๊ตฌ๋…์ด ์„ค์ •๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ทจ์†Œ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 * ์ผ๋ฐ˜์ ์œผ๋กœ ์ด ๋ฉ”์†Œ๋“œ์˜ ๊ตฌํ˜„์€ ํ•ญ๋ชฉ ์ˆ˜์‹ ์„ ํ™œ์„ฑํ™”ํ•˜๊ธฐ ์œ„ํ•ด subscription.request ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
 */
public void onSubscribe(Subscription subscription);

Publisher์™€ Subscriber ์‚ฌ์ด์— ์ฑ„๋„์ด ์—ฐ๊ฒฐ๋˜๋ฉด ์ฒซ ์ด๋ฒคํŠธ๋กœ ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.
Subscription ๊ฐ์ฒด๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ Subscriber์™€ Publisher์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํฌํ•จํ•œ๋‹ค.

public static interface Subscription {
    public void request(long n);
    public void cancel();
}

Publisher๋Š” Subscription ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด Subscriber๋กœ ์ „๋‹ฌํ•˜๋ฉด Subscriber๋Š” ์ด๋ฅผ ์ด์šฉํ•ด Publisher๋กœ ์ •๋ณด๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

CompletableFuture ์ ์šฉํ•ด๋ณด๊ธฐ

public class Shop {
    public double getPrice(String product) {
        return calculatePrice(product);
    }

    public Future<Double> getPriceAsync(String product) {
        CompletableFuture<Double> futurePrice = new CompletableFuture<>();
        new Thread(() -> {
            double price = calculatePrice(product);
            futurePrice.complete(price);
        }).start();
        return futurePrice;
    }

    private double calculatePrice(String product) {
        delay();
        return new Random().nextDouble() * product.charAt(0) + product.charAt(1);
    }
}

@Test
void getPriceAsync() {
    Shop shop = new Shop();
    long start = System.nanoTime();
    Future<Double> myFavorite = shop.getPriceAsync("my favorite");
    long invocationTime = ((System.nanoTime() - start) / 1_000_000);
    System.out.println("invocation returned after " + invocationTime + " msecs");

    System.out.println("doSomethingElse");

    try {
        Double price = myFavorite.get();
        System.out.printf("Price is %.2f\n", price);
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }

    long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
    System.out.println("Price returned after " + retrievalTime + " msecs");
}

์œ„์˜ ์˜ˆ์ œ์—์„œ ์—๋Ÿฌ๋ฅผ ์˜ฌ๋ฐ”๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์ž
๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•˜๋Š” ๋™์•ˆ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ์Šค๋ ˆ๋“œ์—๋งŒ ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค.
์ฆ‰ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ์€ ๊ณ„์† ์ง„ํ–‰๋˜๋ฉฐ ์ผ์˜ ์ˆœ์„œ๊ฐ€ ๊ผฌ์ธ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” get()์ด ๋ฐ˜ํ™˜๋  ๋•Œ๊นŒ์ง€ ์˜์›ํžˆ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋  ์ˆ˜๋„ ์žˆ๋‹ค.

์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ๋Š” ํƒ€์ž„์•„์›ƒ์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—๋Ÿฌ๊ฐ€ ์™œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก completeExceptionally ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ CompletableFuture ๋‚ด๋ถ€์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
@DisplayName("์—ฌ๋Ÿฌ Shop์˜ ๊ฐ€๊ฒฉ์„ ๊ณ„์‚ฐํ•  ๋•Œ ๋น„๋ธ”๋ก ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•˜๊ธฐ")
void nonblock() {
    List<Shop> shops = List.of(
        new Shop("a"),
        new Shop("b"),
        new Shop("c"),
        new Shop("d"),
        new Shop("e")
    );
    long start = System.nanoTime();
    String product = "iPhone";
    List<String> single = shops.stream()
            .map(shop -> String.format("%s price is %.2f", shop.name, shop.getPrice(product)))
            .toList();

    long duration = ((System.nanoTime() - start) / 1_000_000);
    System.out.println("stream process duration " + duration + " msecs");
    // ์œ„์˜ ๊ฒฐ๊ณผ๋Š” ๊ฐ ์ƒ์ ๋งˆ๋‹ค 1์ดˆ์”ฉ ๋”œ๋ ˆ์ด๊ฐ€ ์กด์žฌํ•˜์—ฌ ์ตœ์†Œ 5์ดˆ ์ด์ƒ์ด๋‹ค.
    // stream process duration 5041 msecs


    long start1 = System.nanoTime();
    List<String> blockingParallel = shops.parallelStream()
            .map(shop -> String.format("%s price is %.2f", shop.name, shop.getPrice(product)))
            .toList();

    long duration1 = ((System.nanoTime() - start1) / 1_000_000);
    System.out.println("blockingParallel process duration1 " + duration1 + " msecs");
    // blockingParallel process duration1 1009 msecs

    // ๋ฆฌ์ŠคํŠธ์˜ CompletableFuture๋Š” ๊ฐ๊ฐ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๊ฐ€ ๋๋‚œ ์ƒ์ ์˜ ์ด๋ฆ„ ๋ฌธ์ž์—ด์„ ํฌํ•จํ•œ๋‹ค.
    // ํ•˜์ง€๋งŒ ํ•„์š”ํ•œ ๋ฐ˜ํ™˜ ํƒ€์ž…์€ List<String>์ด๋ฏ€๋กœ ๋ชจ๋“  CompletableFuture์˜ ๋™์ž‘์ด ์™„๋ฃŒ๋˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ถ”์ถœํ•œ ๋‹ค์Œ์— ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•œ๋‹ค.
    // ์ฆ‰, ๋ฆฌ์ŠคํŠธ์˜ ๋ชจ๋“  CompletableFuture์— join์„ ํ˜ธ์ถœํ•ด์„œ ๋ชจ๋“  ๋™์ž‘์ด ๋๋‚˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.
    // CompletableFuture์˜ join๋ฉ”์„œ๋“œ๋Š” Future์ธํ„ฐํŽ˜์ด์Šค์˜ get ๋ฉ”์„œ๋“œ์™€ ๊ฐ™์€ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š”๋‹ค.
    // ๋‹ค๋งŒ join์€ ์•„๋ฌด ์˜ˆ์™ธ๋„ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด ๋‹ค๋ฅด๋‹ค.
    // ๋”ฐ๋ผ์„œ map์˜ ๋žŒ๋‹ค ํ‘œํ˜„์‹์„ try/catch๋กœ ๊ฐ์Œ€ ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒƒ์ด๋‹ค.
    long start2 = System.nanoTime();
    List<CompletableFuture<String>> futures = shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop.name, shop.getPrice(product))))
            .toList();
    List<String> strings = futures.stream()
            .map(CompletableFuture::join)
            .toList();
    long duration2 = ((System.nanoTime() - start2) / 1_000_000);
    System.out.println("futures process duration2 " + duration2 + " msecs");
    // futures process duration2 1010 msecs
}

์œ—๋ถ€๋ถ„์€ ์ˆœ์ฐจ์ ์œผ๋กœ ํ‰๊ฐ€๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ๋‹จ์ผ ํŒŒ์ดํ”„๋ผ์ธ ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ํ‘œํ˜„ํ•œ ๊ฒƒ์ด๋‹ค. (์ ์„ ์œผ๋กœ ํ‘œ์‹œ๋œ ๋ถ€๋ถ„์ด ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ํ‘œํ˜„ํ•œ ๊ฒƒ์ด๋‹ค.)
์ฆ‰, ์ด์ „ ์š”์ฒญ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„์ „ํžˆ ๋๋‚œ ๋‹ค์Œ์— ์ƒˆ๋กœ ๋งŒ๋“  CompletableFuture๊ฐ€ ์ฒ˜๋ฆฌ๋œ๋‹ค.
๋ฐ˜๋ฉด ์•„๋ž˜์ชฝ์€ ์šฐ์„  CompletableFuture๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ชจ์€ ๋‹ค์Œ์— ๋‹ค๋ฅธ ์ž‘์—…๊ณผ๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ์ž์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ชจ์Šต์ด๋‹ค.
๊ฒฐ๊ตญ CompletableFuture๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋จผ์ € ๋ชจ์•„๋†“๊ณ  ๋น„๋™๊ธฐ๋กœ ํƒœ์Šคํฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์„ ๋ฒŒ์–ด๋†“์€ ๊ฒƒ์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งˆ์ง€๋ง‰ futures์˜ ์—ฐ์‚ฐ์—์„œ ํ•˜๋‚˜์˜ ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ  ๋‘ ๊ฐœ์˜ ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค๋Š” ์ ์— ์ฃผ๋ชฉํ•ด์•ผ ํ•œ๋‹ค.
์ŠคํŠธ๋ฆผ ์—ฐ์‚ฐ์€ ๊ฒŒ์œผ๋ฅธ ํŠน์„ฑ์ด ์žˆ์œผ๋ฏ€๋กœ ํ•˜๋‚˜์˜ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ์—ฐ์‚ฐ์„ ์ฒ˜๋ฆฌํ–ˆ๋‹ค๋ฉด ๋ชจ๋“  ๊ฐ€๊ฒฉ ์ •๋ณด ์š”์ฒญ ๋™์ž‘์ด ๋™๊ธฐ์ , ์ˆœ์ฐจ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๊ฒฐ๊ณผ๊ฐ€ ๋œ๋‹ค.
CompletableFuture๋กœ ๊ฐ ์ƒ์ ์˜ ์ •๋ณด๋ฅผ ์š”์ฒญํ•  ๋•Œ ๊ธฐ์กด ์š”์ฒญ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์–ด์•ผ join์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด์„œ ๋‹ค์Œ ์ƒ์ ์œผ๋กœ ์ •๋ณด๋ฅผ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ŠคํŠธ๋ฆผ ๋ณ‘๋ ฌํ™”์™€ CompletableFuture ๋ณ‘๋ ฌํ™”
๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ปฌ๋ ‰์…˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์ปฌ๋ ‰์…˜์„ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ CompletableFuture ๋‚ด๋ถ€์˜ ์—ฐ์‚ฐ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๊ณผ ์–ด๋–ค ๊ฒƒ์„ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ?

  1. CompletableFuture๋ฅผ ์ด์šฉํ•˜๋ฉด ์ „์ฒด์ ์ธ ๊ณ„์‚ฐ์ด ๋ธ”๋ก๋˜์ง€ ์•Š๋„๋ก ์Šค๋ ˆ๋“œ ํ’€์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. I/O๊ฐ€ ํฌํ•จ๋˜์ง€ ์•Š์€ ๊ณ„์‚ฐ ์ค‘์‹ฌ์˜ ๋™์ž‘์ด๋ผ๋ฉด ์ŠคํŠธ๋ฆผ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๊ฐ€์žฅ ๊ตฌํ˜„ํ•˜๊ธฐ ๊ฐ„๋‹จํ•˜๋ฉฐ ํšจ์œจ์ ์ผ ์ˆ˜ ์žˆ๋‹ค.
  3. I/O๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ž‘์—…์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•  ๋•Œ๋Š” CompletableFuture๊ฐ€ ๋” ๋งŽ์€ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๋ฉฐ W/C์˜ ๋น„์œจ์— ์ ํ•ฉํ•œ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šค๋ ˆ๋“œ ํ’€ ํฌ๊ธฐ ์กฐ์ ˆ
์ž๋ฐ” ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ(์ฑ…)์—์„œ ์Šค๋ ˆ๋“œ ํ’€์˜ ์ตœ์ ๊ฐ’์„ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•œ๋‹ค.
์Šค๋ ˆ๋“œ ํ’€์ด ๋„ˆ๋ฌด ํฌ๋ฉด CPU์™€ ๋ฉ”๋ชจ๋ฆฌ ์ž์›์„ ์„œ๋กœ ๊ฒฝ์Ÿํ•˜๋Š๋ผ ์‹œ๊ฐ„์„ ๋‚ญ๋น„ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋ฐ˜๋ฉด ์Šค๋ ˆ๋“œ ํ’€์ด ๋„ˆ๋ฌด ์ž‘์œผ๋ฉด CPU์˜ ์ผ๋ถ€ ์ฝ”์–ด๋Š” ํ™œ์šฉ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
threads = nCPU * uCPU * (1 + W/C)
nCPU๋Š” Runtime.getRuntime().availableProcessors()๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ฝ”์–ด์˜ ์ˆ˜
uCPU๋Š” 0๊ณผ 1์‚ฌ์ด์˜ ๊ฐ’์„ ๊ฐ–๋Š” CPU ํ™œ์šฉ ๋น„์œจ
W/C๋Š” ๋Œ€๊ธฐ์‹œ๊ฐ„๊ณผ ๊ณ„์‚ฐ์‹œ๊ฐ„์˜ ๋น„์œจ

์œ„์˜ ์ƒ์ ์—์„œ ์ƒํ’ˆ์„ ์ฐพ๋Š” ์‹œ๊ฐ„์€ 99ํผ์„ผํŠธ์˜ ์‹œ๊ฐ„์„ ๊ธฐ๋‹ค๋ฆฌ๋ฏ€๋กœ W/C ๋น„์œจ์„ 100์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ณ , CPU ํ™œ์šฉ๋ฅ ์„ 100ํผ์„ผํŠธ, ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”์–ด์˜ ์ˆ˜๋Š” 4๋ผ๋ฉด 400๊ฐœ์˜ ์Šค๋ ˆ๋“œ ๋ฅผ ๊ฐ–๋Š” ์Šค๋ ˆ๋“œ ํ’€์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.
ํ•˜์ง€๋งŒ ์ƒ์  ์ˆ˜ ๋ณด๋‹ค ๋งŽ์€ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ๋ด์•ผ ์‚ฌ์šฉํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์ „ํ˜€ ์—†์œผ๋ฏ€๋กœ ์ƒ์  ์ˆ˜๋ณด๋‹ค ๋งŽ์€ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ–๋Š” ๊ฒƒ์€ ๋‚ญ๋น„์ผ ๋ฟ์ด๋‹ค.

๋น„๋™๊ธฐ ์ž‘์—… ํŒŒ์ดํ”„๋ผ์ธ ๋งŒ๋“ค๊ธฐ

@Test
void discount() {
    List<Shop> shops = List.of(
            new Shop("a"),
            new Shop("b"),
            new Shop("c"),
            new Shop("d"),
            new Shop("e"),
            new Shop("f"),
            new Shop("g"),
            new Shop("h")
    );
    final Executor executor = Executors.newFixedThreadPool(shops.size());
    final String product = "iPhone";

    List<String> collect = shops.stream()
            .map(shop -> shop.getPriceOfCode(product))
            .map(Quote::parse)
            .map(Discount::applyDiscount)
            .toList();

    List<CompletableFuture<String>> collect1 = shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(
                    () -> shop.getPriceOfCode(product), executor))
            .map(future -> future.thenApply(Quote::parse))
            .map(future -> future.thenCompose(quote ->
                    CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor)))
            .toList();

    for(CompletableFuture<String> future : collect1) {
        assertThat(future.isDone()).isEqualTo(false);
    }

    List<String> list = collect1.stream()
            .map(CompletableFuture::join)
            .toList();

    for (int i = 0; i < collect1.size(); i++) {
        CompletableFuture<String> future = collect1.get(i);
        assertThat(future.isDone()).isEqualTo(true);

        String e = list.get(i);
        assertThat(e).isNotEmpty();
    }
}

List์— ๋‹ด๊ฒจ์žˆ๋Š” CompletableFuture<String>์€ (join์ด ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— ์–ธ์ œ ํ˜ธ์ถœ๋ ์ง€๋Š” ๋ชจ๋ฅด์ง€๋งŒ) ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ƒ์ ์—์„œ ์ •๋ณด๋ฅผ ์กฐํšŒํ•œ๋‹ค.
thenApply()๋Š” CompletableFuture๊ฐ€ ๋๋‚  ๋•Œ ๊นŒ์ง€ ๋ธ”๋กํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, CompletableFuture๊ฐ€ ๋™์ž‘์„ ์™„์ „ํžˆ ์™„๋ฃŒํ•œ ๋‹ค์Œ์— thenApply ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌ๋œ ๋žŒ๋‹ค ํ‘œํ˜„์‹์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
๋”ฐ๋ผ์„œ CompletableFuture<String>์„ CompletableFuture<Quote>๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

์œ„์—์„œ ์กฐํ•ฉํ•œ thenCompose()๋Š” Async ๋ฒ„์ „๋„ ์กด์žฌํ•œ๋‹ค.

public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
    return uniComposeStage(defaultExecutor(), fn);
}

Aysnc๋กœ ๋๋‚˜์ง€ ์•Š๋Š” ๋ฉ”์„œ๋“œ๋Š” ์ด์ „ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ์Šค๋ ˆ๋“œ์™€ ๊ฐ™์€ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…์„ ์‹คํ–‰ํ•จ์„ ์˜๋ฏธ ํ•˜๋ฉฐ Async๋กœ ๋๋‚˜๋Š” ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ ์ž‘์—…์ด ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋„๋ก ์Šค๋ ˆ๋“œ ํ’€๋กœ ์ž‘์—…์„ ์ œ์ถœํ•œ๋‹ค.
์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” ์Šค๋ ˆ๋“œ ์ „ํ™˜ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ ๊ฒŒ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด thenCompose๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋œ ๋‘ ๊ฐœ์˜ CompletableFuture ํ•ฉ์น˜๊ธฐ

์œ„์—์„œ๋Š” thenCompose๋ฅผ ํ†ตํ•ด ์ฒซ ๋ฒˆ์งธ CompletableFuture๊ฐ€ ๋๋‚˜๋ฉด ๊ทธ ๊ฒฐ๊ด๊ฐ’์„ ๊ฐ€์ง€๊ณ  ๋‹ค์Œ CompletableFuture๋ฅผ ์‹คํ–‰์‹œ์ผœ ๋ณด์•˜๋‹ค.
ํ•˜์ง€๋งŒ ๋‘ ๊ฐœ์˜ CompletableFuture๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ  ํ•ฉ์น˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

์˜ˆ๋ฅผ ๋“ค์–ด, ํ•œ ์˜จ๋ผ์ธ ์ƒ์ ์—์„œ ๊ณ ๊ฐ์—๊ฒŒ ์œ ๋กœ ๊ฐ€๊ฒฉ์„ ๋ณด์—ฌ์ค„ ๋•Œ ๋‹ฌ๋Ÿฌ ๊ฐ€๊ฒฉ๊ณผ ํ™˜์œจ์„ ์™ธ๋ถ€ ์„œ๋น„์Šค๋ฅผ ํ†ตํ•ด ์žฌ๊ณตํ•ด์ค˜์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž.

@Test
void futurePrice() {
    final Shop shop = new Shop("a");
    final String product = "iPhone";
    CompletableFuture<Double> doubleCompletableFuture = CompletableFuture
            .supplyAsync(() -> shop.getPrice(product))
            .thenCombine(
                CompletableFuture.supplyAsync(() -> getRate(Money.EUR, Money.USD)),
                (price, rate) -> price * rate
            );
    Double result = doubleCompletableFuture.join();
}

@Test
void java7_futurePrice() {
    final Shop shop = new Shop("a");
    final String product = "iPhone";
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<Double> futureRate = executorService.submit(new Callable<Double>() {
        @Override
        public Double call() throws Exception {
            return getRate(Money.EUR, Money.USD);
        }
    });
    Future<Double> futureResult = executorService.submit(new Callable<Double>() {
        @Override
        public Double call() throws Exception {
            double priceInUSD = shop.getPrice(product);
            return priceInUSD * futureRate.get();
        }
    });
}

thenCombine์—๋„ Aysnc ๋™์ž‘์„ ์ง€์›ํ•˜๋Š” ๋ฉ”์„œ๋“œ thenCombineAsync๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” BiFunction (CompletableFuture ๊ฒฐ๊ณผ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ฉ์น ์ง€ ์ •์˜)๋ฅผ ์Šค๋ ˆ๋“œ ํ’€๋กœ ์ œ์ถœ๋˜๋ฉด์„œ ๋ณ„๋„์˜ ํƒœ์Šคํฌ์—์„œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰๋œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์ž๋ฐ” 7์—์„œ ์‚ฌ์šฉํ•œ ๋ฐฉ๋ฒ•๊ณผ ๋น„๊ตํ•˜๋ฉด CompletableFuture๋ฅผ ํ†ตํ•ด ๋ณต์žกํ•œ ์—ฐ์‚ฐ ์ˆ˜ํ–‰ ๋ฐฉ๋ฒ•์„ ํšจ๊ณผ์ ์œผ๋กœ ์‰ฝ๊ฒŒ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

ํƒ€์ž„์•„์›ƒ ์‚ฌ์šฉํ•˜๊ธฐ

Future์˜ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆด๋•Œ๋Š” ๋ฌดํ•œ์ • ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ธ”๋ก์„ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
์ž๋ฐ” 9์—์„œ ์ถ”๊ฐ€๋œ ํƒ€์ž„์•„์›ƒ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
void futurePrice() {
    final Shop shop = new Shop("a");
    final String product = "iPhone";
    CompletableFuture<Double> doubleCompletableFuture = CompletableFuture
            .supplyAsync(() -> shop.getPrice(product))
            .thenCombine(
                CompletableFuture.supplyAsync(() -> getRate(Money.EUR, Money.USD)),
                (price, rate) -> price * rate
            )
            .orTimeout(3, TimeUnit.SECONDS);
    Double result = doubleCompletableFuture.join();
}

orTimeout๋ฉ”์„œ๋“œ๋Š” ์ง€์ •๋œ ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„์— CompletableFuture๋ฅผ TimeoutException์œผ๋กœ ์™„๋ฃŒํ•˜๋ฉด์„œ ๋˜ ๋‹ค๋ฅธ CompletableFuture๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‚ด๋ถ€์ ์œผ๋กœ ScheduledThreadExecutor๋ฅผ ํ™œ์šฉํ•œ๋‹ค.
์ด ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ณ„์‚ฐ ํŒŒ์ดํ”„๋ผ์ธ์„ ์—ฐ๊ฒฐํ•˜๊ณ  ์—ฌ๊ธฐ์„œ TimeoutException์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  completeOnTimeout() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒ ์‹œ ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•˜์—ฌ ๋‹ค์Œ CompletableFuture์— ๋Œ€ํ•œ ํƒœ์Šคํฌ๋„ ๊ณ„์† ์ง„ํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
void futurePrice() {
    final Shop shop = new Shop("a");
    final String product = "iPhone";
    CompletableFuture<Double> doubleCompletableFuture = CompletableFuture
            .supplyAsync(() -> shop.getPrice(product))
            .thenCombine(
                CompletableFuture.supplyAsync(() -> getRate(Money.EUR, Money.USD))
                                .completeOnTimeout(DEFAULT_RATE, 1, TimeUnit.SECONDS),
                (price, rate) -> price * rate
            )
            .orTimeout(3, TimeUnit.SECONDS);
    Double result = doubleCompletableFuture.join();
}

CompletableFuture์˜ ์ข…๋ฃŒ์— ๋Œ€์‘ํ•˜๋Š” ๋ฐฉ๋ฒ•

๋งŒ์•ฝ ์—ฌ๋Ÿฌ ์ƒ์ ์—์„œ ๊ฐ€๊ฒฉ์„ ์กฐํšŒํ•  ๋•Œ ์ผ๋ถ€ ์ƒ์ ์€ ์‘๋‹ต์„ ๋น ๋ฅด๊ฒŒ ์คฌ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ ๋ชจ๋“  ์ƒ์ ์—์„œ ๊ฐ€๊ฒฉ ์กฐํšŒ๊ฐ€ ๋๋‚ฌ์„ ๋•Œ ๊นŒ์ง€ ๊ฐ€๋””๋ฆฌ์ง€ ์•Š๊ณ  ๊ฐ€๊ฒฉ ์ •๋ณด ์‘๋‹ต์ด ๋๋‚  ๋•Œ ๋งˆ๋‹ค ์ฆ‰์‹œ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋” ์ข‹์•„์งˆ ๊ฒƒ์ด๋‹ค.

@Test
void findPricesStream() {
    List<Shop> shops = List.of(
            new Shop("a"),
            new Shop("b"),
            new Shop("c"),
            new Shop("d"),
            new Shop("e")
    );
    final String product = "iPhone";
    ExecutorService executorService = Executors.newCachedThreadPool();

    long start = System.nanoTime();
    CompletableFuture[] futures = shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPriceOfCodeAndRandomDelay(product), executorService))
            .map(future -> future.thenApply(Quote::parse))
            .map(future -> future.thenCompose(futureQuote ->
                    CompletableFuture.supplyAsync(() -> Discount.applyDiscount(futureQuote), executorService))
            )
            .map(future -> future.thenAccept(it -> System.out.printf("%s (done in %s msecs)\n", it, (System.nanoTime() - start) / 1_000_000)))
            .toArray(CompletableFuture[]::new);

    CompletableFuture.allOf(futures).join();
//        ์ฒซ ๋ฒˆ์จฐ
//        d price is 68.425 (done in 2777 msecs)
//        a price is 81.605 (done in 2887 msecs)
//        e price is 118.61 (done in 2910 msecs)
//        b price is 73.967 (done in 3169 msecs)
//        c price is 154.60649999999998 (done in 3350 msecs)
//        ๋‘ ๋ฒˆ์งธ
//        c price is 162.22 (done in 2558 msecs)
//        e price is 135.43200000000002 (done in 2588 msecs)
//        a price is 132.752 (done in 2762 msecs)
//        d price is 114.08800000000001 (done in 3188 msecs)
//        b price is 125.229 (done in 3345 msecs)
}

์œ„์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด shop๋งˆ๋‹ค ๋ฐ˜ํ™˜๋˜๋Š” ์ˆœ์„œ์™€ ์‹œ๊ฐ„์ด ์„œ๋กœ ๋‹ค๋ฅด๊ณ  ๋จผ์ € ์‘๋‹ตํ•˜๋Š” ์ •๋ณด๋ถ€ํ„ฐ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

thenAccept()๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด CompletableFuture์˜ ๊ณ„์‚ฐ์ด ๋๋‚˜๋ฉด ๊ฐ’์„ ์†Œ๋น„ํ•˜๋Š” ์ž‘์—…์„ Consumer ์ธ์ˆ˜๋ฅผ ํ†ตํ•ด ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋‹ค๋ฅธ ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ thenAceeptAsync()๋„ ์กด์žฌํ•œ๋‹ค.

์ถ”๊ฐ€์ ์œผ๋กœ allOf๋ฅผ ํ†ตํ•ด CompletableFuture<Void> ํƒ€์ž…์˜ ๋ชจ๋“  ํƒœ์Šคํฌ๊ฐ€ ์‹คํ–‰ ์™„๋ฃŒ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.
๋ฐ˜๋Œ€๋กœ anyOf๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆ„์œผ๋กœ ์™„๋ฃŒํ•œ CompletableFuture<Object> ํƒ€์ž…์„ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

์œ„์˜ ๋ชจ๋“  ์˜ˆ์ œ๋Š” ์—ฌ๊ธฐ์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ž๋ฐ” 8 ์ธํ”„๋Ÿฐ ๊ฐ•์˜ ์˜ˆ์ œ

  • runAsync() - ๋ฆฌํ„ด ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ
  • supplyAsync() - ๋ฆฌํ„ด ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ
    • Supplier๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›์•„ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•ด์„œ CompletableFuture๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • ForkJoinPool์˜ Executor ์ค‘ ํ•˜๋‚˜๊ฐ€ Supplier๋ฅผ ์‹คํ–‰ํ•  ๊ฒƒ์ด๋‹ค.
  • ์›ํ•˜๋Š” Executor(Thread Pool)๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ๊ธฐ๋ณธ์€ ForkJoinPool.commonPool()
    • ForkJoinPool - JAVA7
public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();
    completableFuture.complete("test");
    // ์œ„์™€ ๋™์ผํ•œ ์ฝ”๋“œ์ด๋‹ค.
    CompletableFuture<String> completableFuture1 = CompletableFuture.completedFuture("test");

    // runAsync - ๋ฆฌํ„ด ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ
    CompletableFuture<Void> completableFuture2 = CompletableFuture.runAsync(() -> {
        System.out.println("Hello " + Thread.currentThread().getName());
    });
    completableFuture2.get();
    // ์ถœ๋ ฅ
    // Hello ForkJoinPool.commonPool-worker-3

    // supplyAsync() - ๋ฆฌํ„ด ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ
    CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(() -> {
        return "return Value!!!";
    });
    System.out.println(completableFuture3.get());
    // ์ถœ๋ ฅ
    // return Value!!!
}

์ฝœ๋ฐฑ ์ œ๊ณตํ•˜๊ธฐ

thenApply(Function)

  • ๋ฆฌํ„ด ๊ฐ’์„ ๋ฐ›์•„์„œ ๋‹ค๋ฅธ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋Š” ์ฝœ๋ฐฑ
public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("Return : " + Thread.currentThread().getName());
        return "return Value!!!";
    }).thenApply((s) -> {
        System.out.println("Then Apply : " + Thread.currentThread().getName());
        return s.toUpperCase();
    });

    // get์„ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฉด ์•„๋ฌด์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค.
    String s = completableFuture.get();
    System.out.println(s);
    // ์ถœ๋ ฅ
    // Return : ForkJoinPool.commonPool-worker-3
    // Then Apply : ForkJoinPool.commonPool-worker-3
    // RETURN VALUE!!!
}

thenAccept(Consumer)

  • ๋ฆฌํ„ด ๊ฐ’์œผ๋กœ ๋˜ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝœ๋ฐฑ (๋ฆฌํ„ด์—†์ด)
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("Return : " + Thread.currentThread().getName());
            return "return Value!!!";
        }).thenAccept((s) -> {
            System.out.println("Then Accept : " + Thread.currentThread().getName());
            System.out.println("Then Accept To UpperCase : " + s.toUpperCase());
        });

        completableFuture.get();
        // ์ถœ๋ ฅ
        // Return : ForkJoinPool.commonPool-worker-3
        // Then Accept : ForkJoinPool.commonPool-worker-3
        // Then Accept To UpperCase : RETURN VALUE!!!
    }

thenRun(Runnable)

  • ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝœ๋ฐฑ
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("Return : " + Thread.currentThread().getName());
            return "return Value!!!";
        }).thenRun(() -> {
            // Runnable
            System.out.println("Then Run : " + Thread.currentThread().getName());
        });

        completableFuture.get();
        // ์ถœ๋ ฅ
        // Return : ForkJoinPool.commonPReturn : ForkJoinPool.commonPool-worker-3
        // Then Run : ForkJoinPool.commonPool-worker-3
    }

์ฝœ๋ฐฑ ์ž์ฒด๋ฅผ ๋˜ ๋‹ค๋ฅธ Thread์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ForkJoinPool์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋งŒ๋“  Thread๋ฅผ ์ œ๊ณตํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ExecutorService๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜์—ฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋ฉด ๋œ๋‹ค.
public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(4);
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("Return : " + Thread.currentThread().getName());
        return "return Value!!!";
    } , executorService).thenRun(() -> {
        // Runnable
        System.out.println("Then Run : " + Thread.currentThread().getName());
    });

    completableFuture.get();
    executorService.shutdown();
//        ์ถœ๋ ฅ
//        Return : pool-1-thread-1
//        Then Run : pool-1-thread-1
}

์กฐํ•ฉํ•˜๊ธฐ

thenCompose()

  • ๋‘ ์ž‘์—…์ด ์„œ๋กœ ์ด์–ด์„œ ์‹คํ–‰ํ•˜๋„๋ก ์กฐํ•ฉ
  • ์ฒซ ๋ฒˆ์งธ ์—ฐ์‚ฐ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‘ ๋ฒˆ์งธ ์—ฐ์‚ฐ์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
        System.out.println("Hello : " + Thread.currentThread().getName());
        return "Hello ";
    });

    // hello.thenCompose(s -> getWorld(s));
    CompletableFuture<String> helloWorld =
            hello.thenCompose(AppForCompletableFuture::getWorld);
    System.out.println(helloWorld.get());

    // ์ถœ๋ ฅ
    // Hello : ForkJoinPool.commonPool-worker-3
    // World : ForkJoinPool.commonPool-worker-5
    // Hello  World
}
private static CompletableFuture<String> getWorld(String message) {
    return CompletableFuture.supplyAsync(() -> {
        System.out.println("World : " + Thread.currentThread().getName());
        return message + " World";
    });
}

thenCombine()

  • ๋‘ ์ž‘์—…์„ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๊ณ  ๋‘˜ ๋‹ค ์ข…๋ฃŒ ํ–ˆ์„ ๋•Œ ์ฝœ๋ฐฑ ์‹คํ–‰
public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
        System.out.println("Hello : " + Thread.currentThread().getName());
        return "Hello ";
    });

    CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
        System.out.println("World : " + Thread.currentThread().getName());
        return "World";
    });

    CompletableFuture<String> result = hello.thenCombine(world, (h , w) -> h + " " + w);
    System.out.println(result.get());
    // ์ถœ๋ ฅ
    // Hello : ForkJoinPool.commonPool-worker-3
    // World : ForkJoinPool.commonPool-worker-5
    // Hello  World
}

allOf()

  • ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋ชจ๋‘ ์‹คํ–‰ํ•˜๊ณ  ๋ชจ๋“  ์ž‘์—… ๊ฒฐ๊ณผ์— ์ฝœ๋ฐฑ ์‹คํ–‰
  • allOf๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…์˜ ๊ฒฐ๊ณผ๋ฅผ List๋กœ ๋ฐ˜ํ™˜๋ฐ›๊ธฐ
public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
        System.out.println("Hello : " + Thread.currentThread().getName());
        return "Hello ";
    });

    CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
        System.out.println("World : " + Thread.currentThread().getName());
        return "World";
    });

    List<CompletableFuture> futures = Arrays.asList(hello , world);

    CompletableFuture[] futuresArray
            = futures.toArray(new CompletableFuture[futures.size()]);

    // ๊ฒฐ๊ณผ ํƒ€์ž…๋“ค์ด ๋ชจ๋‘ ๋™์ผํ•ด์•ผํ•œ๋‹ค.
    CompletableFuture<List<Object>> listCompletableFuture =
            CompletableFuture.allOf(futuresArray).thenApply(v -> {
                return futures.stream()
                        .map(CompletableFuture::join)
                        .collect(Collectors.toList());
            });

    listCompletableFuture.get().forEach(System.out::println);

    // ์ถœ๋ ฅ
    // Hello : ForkJoinPool.commonPool-worker-3
    // World : ForkJoinPool.commonPool-worker-5
    // Hello
    // World
}

anyOf()

  • ์—ฌ๋Ÿฌ ์ž‘์—… ์ค‘์— ๊ฐ€์žฅ ๋นจ๋ฆฌ ๋๋‚œ ํ•˜๋‚˜์˜ ๊ฒฐ๊ณผ์— ์ฝœ๋ฐฑ ์‹คํ–‰
public static void main(String[] args) throws ExecutionException, InterruptedException {

    CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
        System.out.println("Hello : " + Thread.currentThread().getName());
        return "Hello ";
    });

    CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
        System.out.println("World : " + Thread.currentThread().getName());
        return "World";
    });

    CompletableFuture<Void> future =
            CompletableFuture.anyOf(hello, world).thenAccept(System.out::println);
    future.get();

    // ์ถœ๋ ฅ
    // Hello : ForkJoinPool.commonPool-worker-3
    // World : ForkJoinPool.commonPool-worker-5
    // Hello
}

์˜ˆ์™ธ์ฒ˜๋ฆฌ

exceptionally(Function)

public static void main(String[] args) throws ExecutionException, InterruptedException {
    boolean throwError = true;

    CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
        if(throwError){
            throw new IllegalArgumentException();
        }
        System.out.println("Hello : " + Thread.currentThread().getName());
        return "Hello ";
    }).exceptionally(exceptionType -> {
        System.out.println("Exception Type : " + exceptionType);
        return "Error!";
    });

    System.out.println(hello.get());

    // ์ถœ๋ ฅ
    // Exception Type : java.util.concurrent.CompletionException: java.lang.IllegalArgumentException
    // Error!
}

handle(BiFunction)

public static void main(String[] args) throws ExecutionException, InterruptedException {
    boolean throwError = true;

    CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
        if(throwError){
            throw new IllegalArgumentException();
        }
        System.out.println("Hello : " + Thread.currentThread().getName());
        return "Hello ";
    }).handle((result , exceptionType) -> {
        // ์ฒซ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ - ์ •์ƒ์ ์ธ ๊ฒฝ์šฐ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฒฐ๊ณผ ๊ฐ’
        // ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ - ์˜ˆ์™ธ ๋ฐœ์ƒ์‹œ ์˜ˆ์™ธ
        if(exceptionType != null){
            System.out.println("Exception Type : " + exceptionType);
            return "ERROR !!!";
        }
        return result;
    });

    System.out.println(hello.get());
    // ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ "ERROR !!!" ๋ฅผ ๋ฐ˜ํ™˜
    // ์—์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜์„ ์‹œ "Hello" ๋ฐ˜ํ™˜
}