Skip to content

Latest commit

ย 

History

History
429 lines (353 loc) ยท 23.2 KB

ForkJoin Framework.md

File metadata and controls

429 lines (353 loc) ยท 23.2 KB

์ž๋ฐ” 7์ด ๋“ฑ์žฅํ•˜๊ธฐ ์ „์—๋Š” ๋ฐ์ดํ„ฐ ์ปฌ๋ ‰์…˜์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์› ๋‹ค.

  1. ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ธŒ ํŒŒํŠธ๋กœ ๋ถ„ํ• ํ•œ๋‹ค.
  2. ๋ถ„ํ• ๋œ ์„œ๋ธŒ ํŒŒํŠธ๋ฅผ ๊ฐ๊ฐ์˜ ์Šค๋ ˆ๋“œ๋กœ ํ• ๋‹นํ•œ๋‹ค.
  3. ์Šค๋ ˆ๋“œ๋กœ ํ• ๋‹นํ•œ ๋‹ค์Œ์—๋Š” ์˜๋„์น˜ ์•Š์€ ๋ ˆ์ด์Šค ์ปจ๋””์…˜์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ์ ์ ˆํ•œ ๋™๊ธฐํ™”๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
  4. ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์ณ์•ผํ•œ๋‹ค.

์œ„์˜ ๊ณผ์ •์„ ์‰ฝ๊ฒŒ ๋ณ‘๋ ฌํ™”๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ ์—๋Ÿฌ๋ฅผ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์ด ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€, ์—ฌ๋Ÿฌ ์ฒญํฌ๋กœ ๋ถ„ํ• ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๊ณ  ์ปค์Šคํ…€ Spliterator๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์ž.

๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ

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

private long parallelSum(long n) {
    return Stream.iterate(1L, i -> i + 1)
            .limit(n)
            .parallel()
            .reduce(0L, Long::sum);
}

์‚ฌ์‹ค ์ˆœ์ฐจ ์ŠคํŠธ๋ฆผ์— parallel์„ ํ˜ธ์ถœํ•ด๋„ ์ŠคํŠธ๋ฆผ ์ž์ฒด์—๋Š” ์•„๋ฌด ๋ณ€ํ™”๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค.
๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ์—ฐ์‚ฐ์ด ๋ณ‘๋ ฌ๋กœ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•จ์„ ์˜๋ฏธํ•˜๋Š” ๋ถˆ๋ฆฌ์–ธ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์„ค์ •๋œ๋‹ค.

@Override
public final boolean isParallel() {
    return sourceStage.parallel;
}

์–ด๋–ค ์—ฐ์‚ฐ์„ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜๊ณ  ์–ด๋–ค ์—ฐ์‚ฐ์€ ์ˆœ์ฐจ๋กœ ์‹คํ–‰ํ• ์ง€ ์ˆœ์ฐจ โ†”๏ธŽ ๋ณ‘๋ ฌ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์Šค๋ ˆ๋“œ ํ’€ ์„ค์ •
์ŠคํŠธ๋ฆผ์˜ parallel ๋ฉ”์„œ๋“œ์—์„œ ๋ณ‘๋ ฌ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์Šค๋ ˆ๋“œ๋Š” ์–ด๋””์„œ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์ด๋ฉฐ, ๋ช‡๊ฐœ๋‚˜ ์ƒ์„ฑ๋˜๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๊ณผ์ •์„ ์–ด๋–ป๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ•  ๊ฒƒ์ด๋‹ค.
๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์€ ๋‚ด๋ถ€์ ์œผ๋กœ ForJoinPool ์„ ์‚ฌ์šฉํ•œ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ForJoinPool ์€ ํ”„๋กœ์„ธ์„œ ์ˆ˜, ์ฆ‰ Runtime.getRuntime().availableProcessors()๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์— ์ƒ์‘ํ•˜๋Š” ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ–๋Š”๋‹ค.

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");
์œ„์˜ ์„ค์ • ์ฝ”๋“œ๋Š” ์ „์—ญ์ ์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋ฏ€๋กœ ๋ชจ๋“  ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ ์—ฐ์‚ฐ์— ์˜ํ–ฅ์„ ์ค€๋‹ค.
ํ•˜๋‚˜์˜ ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ •ํ•œ ๊ฐ’์„ ์ง€์ •ํ•˜๊ธด ์–ด๋ ต๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ ๊ธฐ๊ธฐ์˜ ํ”„๋กœ์„ธ์„œ ์ˆ˜์™€ ๊ฐ™์œผ๋ฏ€๋กœ ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋‹ค๋ฉด ๊ธฐ๋ณธ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

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

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

๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์ 

  1. ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด ๋Šฅ์‚ฌ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ผญ ์ง์ ‘ ์ธก์ •ํ•ด์•ผ ํ•œ๋‹ค.
  2. ๋ฐ•์‹ฑ์„ ์ฃผ์˜ํ•˜๋ผ. ์ž๋™ ๋ฐ•์‹ฑ๊ณผ ์–ธ๋ฐ•์‹ฑ์€ ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ์ €ํ•˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์š”์†Œ๋‹ค. ๊ธฐ๋ณธํ˜• ํŠนํ™” ์ŠคํŠธ๋ฆผ์„ ํ™•์ธํ•˜๋ผ.
  3. ์ˆœ์ฐจ ์ŠคํŠธ๋ฆผ๋ณด๋‹ค ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์—์„œ ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๋Š” ์—ฐ์‚ฐ์ด ์žˆ๋‹ค. limit, findFirst ์ฒ˜๋Ÿผ ์š”์†Œ์˜ ์ˆœ์„œ์— ์˜์กดํ•˜๋Š” ์—ฐ์‚ฐ์„ ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์—์„œ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ๋น„์‹ผ ๋น„์šฉ์„ ์น˜๋ค„์•ผ ํ•œ๋‹ค.
  4. ์ŠคํŠธ๋ฆผ์—์„œ ์ˆ˜ํ–‰ํ•˜๋Š” ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์—ฐ์‚ฐ ๋น„์šฉ์„ ๊ณ ๋ คํ•˜๋ผ.
  5. ์†Œ๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ์—์„œ๋Š” ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์ด ๋„์›€ ๋˜์ง€ ์•Š๋Š”๋‹ค.
  6. ์ŠคํŠธ๋ฆผ์„ ๊ตฌ์„ฑํ•˜๋Š” ์ž๋ฃŒ๊ตฌ์กฐ๊ฐ€ ์ ์ ˆํ•œ์ง€ ํ™•์ธํ•˜๋ผ.
  7. ์ŠคํŠธ๋ฆผ์˜ ํŠน์„ฑ๊ณผ ํŒŒ์ดํ”„๋ผ์ธ์˜ ์ค‘๊ฐ„ ์—ฐ์‚ฐ์ด ์ŠคํŠธ๋ฆผ์˜ ํŠน์„ฑ์„ ์–ด๋–ป๊ฒŒ ๋ฐ”๊พธ๋Š”์ง€์— ๋”ฐ๋ผ ๋ถ„ํ•ด ๊ณผ์ •์˜ ์„ฑ๋Šฅ์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค. ํ•„ํ„ฐ ์—ฐ์‚ฐ์œผ๋กœ ์ธํ•ด ์ŠคํŠธ๋ฆผ์˜ ๊ธธ์ด๋ฅผ ์˜ˆ์ธกํ•  ์ˆ˜ ์—†์œผ๋ฉด ์ŠคํŠธ๋ฆผ์„ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„์ง€ ์•Œ ์ˆ˜ ์—†๊ฒŒ ๋œ๋‹ค.
  8. ์ตœ์ข… ์—ฐ์‚ฐ์˜ ๋ณ‘ํ•ฉ ๊ณผ์ • (combiner) ๋น„์šฉ์„ ์‚ดํŽด๋ณด๋ผ.

๊ณต์œ ๋œ ์ƒํƒœ๋ฅผ ๋ฐ”๊พธ๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉ

public long sideEffectSum(long n) {
    Accumulator accumulator = new Accumulator();
    LongStream.rangeClosed(1, n).forEach(accumulator::add);
    return accumulator.total;
}

public class Accumulator {
    public long total = 0;
    public void add(long value) { total += value; }
}

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

ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ

ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ๋ณ‘๋ ฌํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์—…์„ ์žฌ๊ท€์ ์œผ๋กœ ์ž‘์€ ์ž‘์—…์œผ๋กœ ๋ถ„ํ• ํ•œ ๋‹ค์Œ์— ์„œ๋ธŒ ํƒœ์Šคํฌ ๊ฐ๊ฐ์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์ณ์„œ ์ „์ฒด ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค๋„๋ก ์„ค๊ณ„๋˜์—ˆ๋‹ค.
์„œ๋ธŒ ํƒœ์Šคํฌ๋ฅผ ์Šค๋ ˆ๋“œ ํ’€(ForkJoinPool)์˜ ์ž‘์—…์ž ์Šค๋ ˆ๋“œ์— ๋ถ„์‚ฐ ํ• ๋‹นํ•˜๋Š” ExecutorService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

RecursiveTask ํ™œ์šฉ

์Šค๋ ˆ๋“œ ํ’€์„ ์ด์šฉํ•˜๋ ค๋ฉด RecursiveTask<R>์˜ ์„œ๋ธŒํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.
์—ฌ๊ธฐ์„œ R์€ ๋ณ‘๋ ฌํ™”๋œ ํƒœ์ŠคํŠธ๊ฐ€ ์ƒ์„ฑํ•˜๋Š” ๊ฒฐ๊ณผ ํ˜•์‹ ๋˜๋Š” ๊ฒฐ๊ณผ๊ฐ€ ์—†์„ ๋•Œ๋Š” RecursiveAction ํ˜•์‹์ด๋‹ค.

public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
    /**
     * ๊ณ„์‚ฐ ๊ฒฐ๊ณผ 
     */
    V result;

    /**
     * ์ด ์ž‘์—…์œผ๋กœ ์ˆ˜ํ–‰๋˜๋Š” ์ฃผ์š” ๊ณ„์‚ฐ
     */
    protected abstract V compute();

    protected final boolean exec() {
        result = compute();
        return true;
    }
    ...
}

RecursiveTask๋ฅผ ์ •์˜ํ•˜๋ ค๋ฉด ์ถ”์ƒ ๋ฉ”์„œ๋“œ์ธ compute๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
๋Œ€ํ‘œ์ ์ธ ๊ตฌํ˜„์ฒด๋กœ๋Š” DualPivotQuicksort.RunMerger ๊ฐ€ ์žˆ๋‹ค.
ํ•ด๋‹น compute๋ฉ”์„œ๋“œ๋Š” ํƒœ์Šคํฌ๋ฅผ ์„œ๋ธŒํƒœ์Šคํฌ๋กœ ๋ถ„ํ• ํ•˜๋Š” ๋กœ์ง๊ณผ ๋” ์ด์ƒ ๋ถ„ํ• ํ•  ์ˆ˜ ์—†์„ ๋•Œ ๊ฐœ๋ณ„ ์„œ๋ธŒํƒœ์Šคํฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ƒ์‚ฐํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ •์˜ํ•œ๋‹ค.
๋ถ„ํ•  ํ›„ ์ •๋ณต ์•Œ๊ณ ๋ฆฌ์ฆ˜์˜ ๋ณ‘๋ ฌํ™” ๋ฒ„์ „์ด๋‹ค.

if (ํƒœ์Šคํฌ๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ž‘๊ฑฐ๋‚˜ ๋” ์ด์ƒ ๋ถ„ํ• ํ•  ์ˆ˜ ์—†์œผ๋ฉด) {
    ์ˆœ์ฐจ์ ์œผ๋กœ ํƒœ์ŠคํŠธ ๊ณ„์‚ฐ
} else {
    ํƒœ์Šคํฌ๋ฅผ ๋‘ ์„œ๋ธŒํƒœ์Šคํฌ๋กœ ๋ถ„ํ• 
    ํƒœ์Šคํฌ๊ฐ€ ๋‹ค์‹œ ์„œ๋ธŒํƒœ์Šคํฌ๋กœ ๋ถ„ํ• ๋˜๋„๋ก ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํ˜ธ์ถœํ•จ
    ๋ชจ๋“  ์„œ๋ธŒํƒœ์Šคํฌ์˜ ์—ฐ์‚ฐ์ด ์™„๋ฃŒ๋  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ
    ๊ฐ ์„œ๋ธŒํƒœ์Šคํฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์นจ
}

public class ForkJoinSumCalculator extends RecursiveTask<Long> {

    private final long[] numbers;
    private final int start;
    private final int end;
    public static final long THRESHOLD = 10_000;

    private ForkJoinSumCalculator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    public ForkJoinSumCalculator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }

    @Override
    protected Long compute() {
        int length = end - start;
        if (length <= THRESHOLD) {
            return computeSequentially();
        }
        System.out.printf("%s start: %d, end: %d\n", Thread.currentThread().getName(), start, end);
        ForkJoinSumCalculator leftTask =
                new ForkJoinSumCalculator(numbers, start, start + length / 2);
        leftTask.fork();    // ForkJoinPool์˜ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋กœ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ ํƒœ์Šคํฌ๋ฅผ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•œ๋‹ค.

        ForkJoinSumCalculator rightTask =
                new ForkJoinSumCalculator(numbers, start + length / 2, end);
        Long rightResult = rightTask.compute();     // ๋‘ ๋ฒˆ์งธ ์„œ๋ธŒํƒœ์Šคํฌ๋ฅผ ๋™๊ธฐ ์‹คํ–‰ํ•œ๋‹ค. ์ด๋•Œ ์ถ”๊ฐ€๋กœ ๋ถ„ํ• ์ด ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค.
        Long leftResult = leftTask.join();          // ์ฒซ ๋ฒˆ์งธ ์„œ๋ธŒํƒœ์Šคํฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์•„์ง ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋‹ค๋ฆฐ๋‹ค.
        return leftResult + rightResult;
    }

    private Long computeSequentially() {
        long sum = 0;
        for (int i = start ; i < end ; i++) {
            sum += numbers[i];
        }
        return sum;
    }
}

@Test
void forkJoin() {
    long[] numbers = LongStream.rangeClosed(1, 100_000).toArray();
    ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
    Long result = new ForkJoinPool().invoke(task);
    Assertions.assertThat(result).isEqualTo(5000050000L);
}
//        ForkJoinPool-1-worker-1 start: 0, end: 100000
//        ForkJoinPool-1-worker-1 start: 50000, end: 100000
//        ForkJoinPool-1-worker-2 start: 0, end: 50000
//        ForkJoinPool-1-worker-1 start: 75000, end: 100000
//        ForkJoinPool-1-worker-1 start: 87500, end: 100000
//        ForkJoinPool-1-worker-2 start: 25000, end: 50000
//        ForkJoinPool-1-worker-1 start: 75000, end: 87500
//        ForkJoinPool-1-worker-3 start: 0, end: 25000
//        ForkJoinPool-1-worker-4 start: 50000, end: 75000
//        ForkJoinPool-1-worker-2 start: 37500, end: 50000
//        ForkJoinPool-1-worker-4 start: 62500, end: 75000
//        ForkJoinPool-1-worker-4 start: 50000, end: 62500
//        ForkJoinPool-1-worker-3 start: 12500, end: 25000
//        ForkJoinPool-1-worker-5 start: 25000, end: 37500
//        ForkJoinPool-1-worker-6 start: 0, end: 12500

์ƒ์„ฑํ•œ ํƒœ์Šคํฌ๋ฅผ ์ƒˆ๋กœ์šด ForkJoinPool์˜ invoke ๋ฉ”์„œ๋“œ๋กœ ์ „๋‹ฌํ•˜์—ฌ ForkJoinSumCalculator์—์„œ ์ •์˜ํ•œ ํƒœ์Šคํฌ์˜ ๊ฒฐ๊ณผ๋กœ ๋ฐ˜ํ™˜๋ฐ›๋Š”๋‹ค.
์ผ๋ฐ˜์ ์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋‘˜ ์ด์ƒ์˜ ForkJoinPool์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
ํ•ด๋‹น ์Šค๋ ˆ๋“œ ํ’€์„ ํ•œ ๋ฒˆ๋งŒ ์ธ์Šคํ„ด์Šคํ™”ํ•ด์„œ ์ •์  ํ•„๋“œ์— ์‹ฑ๊ธ€ํ†ค์œผ๋กœ ์ €์žฅํ•œ๋‹ค.

ForkJoinPool์„ ๋งŒ๋“ค๋ฉด์„œ ์ธ์ˆ˜๊ฐ€ ์—†๋Š” ๋””ํดํŠธ ์ƒ์„ฑ์ž๋ฅผ ์ด์šฉํ–ˆ๋Š”๋ฐ, ์ด๋Š” JVM์—์„œ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ํ”„๋กœ์„ธ์„œ๊ฐ€ ์ž์œ ๋กญ๊ฒŒ ํ’€์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค.
๋” ์ •ํ™•ํ•˜๊ฒŒ๋Š” Runtime.availableProcessors์˜ ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ํ’€์— ์‚ฌ์šฉํ•  ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
'์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋กœ์„ธ์„œ'๋ผ๋Š” ์ด๋ฆ„๊ณผ ๋‹ฌ๋ฆฌ ์‹ค์ œ ํ”„๋กœ์„ธ์„œ์™ธ์— ํ•˜์ดํผ์Šค๋ ˆ๋”ฉ๊ณผ ๊ด€๋ จ๋œ ๊ฐ€์ƒ ํ”„๋กœ์„ธ์„œ๋„ ๊ฐœ์ˆ˜์— ํฌํ•จ๋œ๋‹ค.

ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ œ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

  1. join ๋ฉ”์„œ๋“œ๋ฅผ ํƒœ์Šคํฌ์— ํ˜ธ์ถœํ•˜๋ฉด ํƒœ์Šคํฌ๊ฐ€ ์ƒ์‚ฐํ•˜๋Š” ๊ฒฐ๊ณผ๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ํ˜ธ์ถœ์ž๋ฅผ ๋ธ”๋ก์‹œํ‚จ๋‹ค.
    • ๋”ฐ๋ผ์„œ ๋‘ ์„œ๋ธŒํƒœ์Šคํฌ๊ฐ€ ๋ชจ๋‘ ์‹œ์ž‘๋œ ๋‹ค์Œ์— join์„ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค.
    • ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ฐ๊ฐ์˜ ์„œ๋ธŒํƒœ์Šคํฌ๊ฐ€ ๋‹ค๋ฅธ ํƒœ์Šคํฌ๊ฐ€ ๋๋‚˜๊ธธ ๊ธฐ๋ผ๋””๋Š” ์ตœ์•…์˜ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.
  2. RecursiveTask ๋‚ด์—์„œ๋Š” ForkJoinPool์˜ invoke๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง์•„์•ผ ํ•œ๋‹ค.
    • ๋Œ€์‹  compute๋‚˜ fork ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์ˆœ์ฐจ ์ฝ”๋“œ์—์„œ ๋ณ‘๋ ฌ ๊ณ„์‚ฐ์„ ์‹œ์ž‘ํ•  ๋•Œ๋งŒ invoke๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  3. ์„œ๋ธŒํƒœ์Šคํฌ์— fork๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ForkJoinPool์˜ ์ผ์ •์„ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์™ผ์ชฝ ์ž‘์—…๊ณผ ์˜ค๋ฅธ์ชฝ ์ž‘์—… ๋ชจ๋‘์— fork ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์ž์—ฐ์Šค๋Ÿฌ์šธ ๊ฒƒ ๊ฐ™์ง€๋งŒ ํ•œ์ชฝ ์ž‘์—…์—๋Š” fork๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค๋Š” compute๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ํšจ์œจ์ ์ด๋‹ค.
    • ๊ทธ๋Ÿฌ๋ฉด ๋‘ ์„œ๋ธŒํƒœ์Šคํฌ์˜ ํ•œ ํƒœ์Šคํฌ์—๋Š” ๊ฐ™์€ ์Šค๋ ˆ๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ’€์—์„œ ๋ถˆํ•„์š”ํ•œ ํƒœ์Šคํฌ๋ฅผ ํ• ๋‹นํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ํ”ผํ•  ์ˆ˜ ์žˆ๋‹ค.
  4. ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ด์šฉํ•˜๋Š” ๋ณ‘๋ ฌ ๊ณ„์‚ฐ์€ ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
  5. ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ์—์„œ ์‚ดํŽด๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ ๋ฉ€ํ‹ฐ์ฝ”์–ด์— ํฌํฌ/์กฐ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ˆœ์ฐจ ์ฒ˜๋ฆฌ๋ณด๋‹ค ๋ฌด์กฐ๊ฑด ๋น ๋ฅผ ๊ฒƒ์ด๋ž€ ์ƒ๊ฐ์€ ๋ฒ„๋ ค์•ผ ํ•œ๋‹ค.
    • ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋ ค๋ฉด ํƒœ์Šคํฌ๋ฅผ ์—ฌ๋Ÿฌ ๋…๋ฆฝ์ ์ธ ์„œ๋ธŒํƒœ์Šคํฌ๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
    • ๊ฐ ์„œ๋ธŒํƒœ์Šคํฌ์˜ ์‹คํ–‰ ์‹œ๊ฐ„์€ ์ƒˆ๋กœ์šด ํƒœ์Šคํฌ๋ฅผ ํฌํ‚นํ•˜๋Š”๋ฐ ๋“œ๋Š” ์‹œ๊ฐ„๋ณด๋‹ค ๊ธธ์–ด์•ผ ํ•œ๋‹ค.
    • JIT ์ปดํŒŒ์ผ๋Ÿฌ์— ์˜ํ•ด ์ตœ์ ํ™”๋˜๋ ค๋ฉด ์›œ์—… ๋˜๋Š” ์‹คํ–‰ ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ•˜๋ฏ€๋กœ ํ”„๋กœ๊ทธ๋žจ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ธก์ •ํ•ด์•ผ ํ•œ๋‹ค.

์ž‘์—… ํ›”์น˜๊ธฐ

์œ„์˜ ์˜ˆ์ œ์—์„œ ์ˆซ์ž๊ฐ€ 10_000๊ฐœ ์ดํ•˜์ด๋ฉด ๋ถ„ํ• ์„ ์ค‘๋‹จํ–ˆ๋Š”๋ฐ, ๋งŒ์•ฝ ์ฒœ๋งŒ ๊ฐœ ํ•ญ๋ชฉ์„ ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด์ด์˜€๋‹ค๋ฉด ForkJoinSumCalculator๋Š” ์ฒœ ๊ฐœ ์ด์ƒ์˜ ์„œ๋ธŒ ํƒœ์Šคํฌ๋ฅผ ํฌํฌํ•  ๊ฒƒ์ด๋‹ค.
์ฝ”์–ด์˜ ๊ฐœ์ˆ˜์— ๋น„ํ•ด ์„œ๋ธŒํƒœ์Šคํฌ๊ฐ€ ์ฒœ ๊ฐœ ์ด์ƒ์œผ๋กœ ๋„ˆ๋ฌด ๋งŽ์•„ ์ž์›๋งŒ ๋‚ญ๋น„ํ•˜๋Š” ๊ฒƒ ๊ฐ™์•„ ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค.
์‹ค์ œ๋กœ๋Š” ์ฝ”์–ด ๊ฐœ์ˆ˜์™€ ๊ด€๊ณ„์—†์ด ์ ์ ˆํ•œ ํฌ๊ธฐ๋กœ ๋ถ„ํ• ๋œ ๋งŽ์€ ํƒœ์Šคํฌ๋ฅผ ํฌํ‚นํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•˜๋‹ค.

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

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

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

public class ForkJoinWorkerThread extends Thread {
    /*
     * ForkJoinWorkerThreads๋Š” ForkJoinPools์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๊ณ  ForkJoinTasks๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
     * ์„ค๋ช…์€ ForkJoinPool ํด๋ž˜์Šค์˜ ๋‚ด๋ถ€ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
     * ์ด ํด๋ž˜์Šค๋Š” ํ’€๊ณผ WorkQueue์— ๋Œ€ํ•œ ๋งํฌ๋งŒ ์œ ์ง€ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
     */
    final ForkJoinPool pool;                // the pool this thread works in
    final ForkJoinPool.WorkQueue workQueue; // work-stealing mechanics
    ...
}

Spliterator ์ธํ„ฐํŽ˜์ด์Šค

์ž๋ฐ” 8์€ Spliterator ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ˜๋ณต์ž ๋ผ๋Š” ์ƒˆ๋กœ์šด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
Iterator์™€ ๊ฐ™์ด ์†Œ์Šค์˜ ์š”์†Œ ํƒ์ƒ‰ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์ ์€ ๊ฐ™์ง€๋งŒ Spliterator๋Š” ๋ณ‘๋ ฌ ์ž‘์—…์— ํŠนํ™”๋˜์–ด ์žˆ๋‹ค.

์ž๋ฐ” 8์€ ์ปฌ๋ ‰์…˜ ํ”„๋ ˆ์ž„์›Œํฌ์— ํฌํ•จ๋œ ๋ชจ๋“  ์ž๋ฃŒ๊ตฌ์กฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋””ํดํŠธ Spilterator ๊ตฌํ˜„์„ ์ œ๊ณตํ•œ๋‹ค.

public interface Spliterator<T> {
    boolean tryAdvance(Consumer<? super T> action);
    Spliterator<T> trySplit();
    long estimateSize();
    int characteristics();
}
  • T๋Š” Spilterator์—์„œ ํƒ์ƒ‰ํ•˜๋Š” ์š”์†Œ์˜ ํ˜•์‹์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค.
  • tryAdvance ๋ฉ”์„œ๋“œ๋Š” Spliterator์˜ ์š”์†Œ๋ฅผ ํ•˜๋‚˜์”ฌ ์ˆœ์ฐจ์ ์œผ๋กœ ์†Œ๋น„ํ•˜๋ฉด์„œ(์ฆ‰, ์ผ๋ฐ˜์ ์ธ Iterator ๋™์ž‘๊ณผ ๊ฐ™๋‹ค.) ํƒ์ƒ‰ํ•ด์•ผ ํ•  ์š”์†Œ๊ฐ€ ๋‚จ์•„์žˆ์œผ๋ฉด ์ฐธ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • trySplit ๋ฉ”์„œ๋“œ๋Š” Spliterator์˜ ์ผ๋ถ€ ์š”์†Œ๋ฅผ ๋ถ„ํ• ํ•ด์„œ ๋‘ ๋ฒˆ์งธ Spliterator๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์„œ๋“œ๋‹ค.
  • estimateSize๋กœ ํƒ์ƒ‰ํ•ด์•ผ ํ•  ์š”์†Œ ์ˆ˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ ํƒ์ƒ‰ํ•ด์•ผ ํ•  ์š”์†Œ ์ˆ˜๊ฐ€ ์ •ํ™•ํ•˜์ง„ ์•Š๋”๋ผ๋„ ์ œ๊ณต๋œ ๊ฐ’์„ ์ด์šฉํ•ด์„œ ๋” ์‰ฝ๊ณ  ๊ณตํ‰ํ•˜๊ฒŒ Spliterator๋ฅผ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ถ„ํ•  ๊ณผ์ •

์œ„์˜ ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด ์ŠคํŠธ๋ฆผ์„ ์—ฌ๋Ÿฌ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ถ„ํ• ํ•˜๋Š” ๊ณผ์ •์€ ์žฌ๊ท€์ ์œผ๋กœ ์ผ์–ด๋‚œ๋‹ค.
trySplit์˜ ๊ฒฐ๊ณผ๊ฐ€ null์ด ๋  ๋•Œ๊นŒ์ง€(๋” ์ด์ƒ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๋ถ„ํ• ํ•  ์ˆ˜ ์—†์Œ) ์ด ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  characteristics๋ผ๋Š” ์ถ”์ƒ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜์—ฌ Spliterator์˜ ํŠน์„ฑ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ปค์Šคํ…€ Spliterator ๊ตฌํ˜„ํ•˜๊ธฐ

/**
 * ์ŠคํŠธ๋ฆผ์— ๋ฆฌ๋“€์‹ฑ์„ ์‹คํ–‰ํ•˜๋ฉด์„œ ๋‹จ์–ด ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋‹ค.
 * ์ง€๊ธˆ๊นŒ์ง€ ๋ฐœ๊ฒฌํ•œ ๋‹จ์–ด ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” counter์™€ ๋งˆ์ง€๋ง‰ ๋ฌธ์ž๊ฐ€ ๊ณต๋ฐฑ์ด์˜€๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ธฐ์–ตํ•˜๋Š” lastSpace ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋‹ค.
 */
public class WordCounter {
    private final int counter;
    private final boolean lastSpace;
    public WordCounter(int counter, boolean lastSpace) {
        this.counter = counter;
        this.lastSpace = lastSpace;
    }

    /**
     * WordCounter ํด๋ž˜์Šค๋ฅผ ์–ด๋–ค ์ƒํƒœ๋กœ ์ƒ์„ฑํ•  ๊ฒƒ์ธ์ง€ ์ •์˜
     * ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๊ณต๋ฐฑ์ด๋ผ๋ฉด lastSpace ์˜ ๊ฐ’์„ ๋ณด๊ณ  ์–ด๋–ค ์ƒํƒœ๋กœ ์ „์ดํ• ์ง€ ์ •์˜ํ•œ ๊ฒƒ์ด๋‹ค.
     */
    public WordCounter accumulate(Character c) {
        if (Character.isWhitespace(c)) {
            return lastSpace ? this : new WordCounter(counter, true);
        } else {
            return lastSpace ? new WordCounter(counter + 1, false) : this;
        }
    }

    /**
     * ๋ฌธ์ž์—ด ์„œ๋ธŒ ์ŠคํŠธ๋ฆผ์„ ์ฒ˜๋ฆฌํ•œ WordCounter์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์นœ๋‹ค.
     * WordCounter์˜ ๋‚ด๋ถ€ counter๊ฐ’์„ ์„œ๋กœ ํ•ฉ์นœ๋‹ค.
     */
    public WordCounter combine(WordCounter wordCounter) {
        return new WordCounter(
            counter + wordCounter.counter,
            wordCounter.lastSpace
        );
    }

    public int getCounter() {
        return this.counter;
    }
}

public class WordCounterSpliterator implements Spliterator<Character> {
    private final String string;
    private int currentIndex = 0;

    public WordCounterSpliterator(String string) {
        this.string = string;
    }

    /**
     * ๋ฌธ์ž์—ด์—์„œ ํ˜„์žฌ ์ธ๋ฑ์Šค์— ํ•ด๋‹นํ•˜๋Š” ๋ฌธ์ž๋ฅผ Consumer์— ์ œ๊ณตํ•œ ๋‹ค์Œ์— ์ธ๋ฑ์Šค๋ฅผ ์ฆ๊ฐ€์‹œํ‚จ๋‹ค.
     * ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋œ Consumer๋Š” ์ŠคํŠธ๋ฆผ์„ ํƒ์ƒ‰ํ•˜๋ฉด์„œ ์ ์šฉํ•ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜ ์ง‘ํ•ฉ์ด ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์†Œ๋น„ํ•œ ๋ฌธ์ž๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์ž๋ฐ” ๋‚ด๋ถ€ ํด๋ž˜์Šค๋‹ค.
     * ์—ฌ๊ธฐ์—์„œ๋Š” ์ŠคํŠธ๋ฆผ์„ ํƒ์ƒ‰ํ•˜๋ฉด์„œ ํ•˜๋‚˜์˜ ๋ฆฌ๋“€์‹ฑ ํ•จ์ˆ˜, ์ฆ‰ WordCounter.accumulate ๋ฉ”์„œ๋“œ๋งŒ ์ ์šฉํ•œ๋‹ค.
     * ์ƒˆ๋กœ์šด ์ปค์„œ ์œ„์น˜๊ฐ€ ์ „์ฒด ๋ฌธ์ž์—ด ๊ธธ์ด๋ณด๋‹ค ์ž‘์œผ๋ฉด ์ฐธ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ ๋ฐ˜๋ณต ํƒ์ƒ‰ํ•ด์•ผ ํ•  ๋ฌธ์ž๊ฐ€ ๋‚จ์•„์žˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค.
     */
    @Override
    public boolean tryAdvance(Consumer<? super Character> action) {
        action.accept(string.charAt(currentIndex++));    // ํ˜„์žฌ ๋ฌธ์ž๋ฅผ ์†Œ๋น„
        return currentIndex < string.length();           // ์†Œ๋น„ํ•  ๋ฌธ์ž๊ฐ€ ๋‚จ์•„์žˆ์œผ๋ฉด true๋ฅผ ๋ฆฌํ„ด
    }

    /**
     * ๋ฐ˜๋ณต๋  ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๋ถ„ํ• ํ•˜๋Š” ๋กœ์ง์„ ํฌํ•จํ•˜๋ฏ€๋กœ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ฉ”์„œ๋“œ๋‹ค.
     * RecursiveTask.compute ๋ฉ”์„œ๋“œ ์—์„œ ํ–ˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ ์šฐ์„  ๋ถ„ํ•  ๋™์ž‘์„ ์ค‘๋‹จํ•  ํ•œ๊ณ„๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.
     * ์—ฌ๊ธฐ์„œ๋Š” 10๊ฐœ์˜ ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์‹ค์ „์—์„œ๋Š” ๋„ˆ๋ฌด ๋งŽ์€ ํƒœ์Šคํฌ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๋„๋ก ๋” ๋†’์€ ํ•œ๊ณ„๊ฐ’์„ ์‚ฌ์šฉํ•ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค.
     * ๋ถ„ํ• ์ด ํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ๋Š” ํŒŒ์‹ฑํ•ด์•ผ ํ•  ๋ฌธ์ž์—ด ์ฒญํฌ์˜ ์ค‘๊ฐ„ ์œ„์น˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ถ„ํ• ํ•˜๋„๋ก ์ง€์‹œํ•œ๋‹ค.
     * ์ด๋•Œ ๋‹จ์–ด ์ค‘๊ฐ„์„ ๋ถ„ํ• ํ•˜์ง€ ์•Š๋„๋ก ๋นˆ ๋ฌธ์ž๊ฐ€ ๋‚˜์˜ฌ๋•Œ๊นŒ์ง€ ๋ถ„ํ•  ์œ„์น˜๋ฅผ ์ด๋™์‹œํ‚จ๋‹ค.
     * ๋ถ„ํ• ํ•  ์œ„์น˜๋ฅผ ์ฐพ์•˜์œผ๋ฉด ์ƒˆ๋กœ์šด Spliterator๋ฅผ ๋งŒ๋“ ๋‹ค.
     * ์ƒˆ๋กœ ๋งŒ๋“  Spliterator๋Š” ํ˜„์žฌ ์œ„์น˜ currentIndex ๋ถ€ํ„ฐ ๋ถ„ํ• ๋œ ์œ„์น˜๊นŒ์ง€์˜ ๋ฌธ์ž๋ฅผ ํƒ์ƒ‰ํ•œ๋‹ค.
     */
    @Override
    public Spliterator<Character> trySplit() {
        int currentSize = string.length() - currentIndex;
        if (currentSize < 10) {
            return null;    // ํŒŒ์‹ฑํ•  ๋ฌธ์ž์—ด์„ ์ˆœ์ฐจ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„ ๋งŒํผ ์ถฉ๋ถ„ํžˆ ์ž‘์•„์กŒ์Œ์„ ์•Œ๋ฆฌ๋Š” null์„ ๋ฐ˜ํ™˜
        }
        for (int splitPos = currentSize / 2 + currentIndex;
             splitPos < string.length() ; splitPos++) {     // ๋ถ„ํ•  ์‹œ์ž‘ ์œ„์น˜๋ฅผ ์ค‘๊ฐ„์œผ๋กœ ์ง€์ •
            if (Character.isWhitespace(string.charAt(splitPos))) {      // ๋‹ค์Œ ๊ณต๋ฐฑ์ด ๋‚˜์˜ฌ๋•Œ๊นŒ์ง€ ๋ถ„ํ•  ์œ„์น˜๋ฅผ ๋’ค๋กœ ์ด๋™
                // ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ถ„ํ•  ์œ„์น˜๊นŒ์ง€ ๋ฌธ์ž์—ด์„ ํŒŒ์‹ฑํ•  ์ƒˆ๋กœ์šด WordCountSpliterator ๋ฅผ ์ƒ์„ฑ
                Spliterator<Character> spliterator =
                        new WordCounterSpliterator(string.substring(currentIndex, splitPos));

                currentIndex = splitPos; // ์‹œ์ž‘ ์œ„์น˜๋ฅผ ๋ถ„ํ•  ์œ„์น˜๋กœ ์„ค์ •
                return spliterator;     // ๊ณต๋ฐฑ์„ ์ฐพ์•˜๊ณ  ๋ฌธ์ž์—ด์„ ๋ถ„๋ฆฌํ–ˆ์œผ๋ฏ€๋กœ ๋ฃจํ”„๋ฅผ ์ข…๋ฃŒ
            }
        }
        return null;
    }

    /**
     * Spliterator๊ฐ€ ํŒŒ์‹ฑํ•  ๋ฌธ์ž์—ด ์ „์ฒด ๊ธธ์ด์™€ ํ˜„์žฌ ๋ฐ˜๋ณต ์ค‘์ธ ์œ„์น˜์˜ ์ฐจ์ด๋‹ค.
     */
    @Override
    public long estimateSize() {
        return string.length() - currentIndex;
    }

    @Override
    public int characteristics() {
        return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
    }
}

@Test
void whiteSpaceCount() {
    // 1. ์ฐจ๋ก€๋Œ€๋กœ ํƒ์ƒ‰
    final String content = "Lorem Ipsum is    simply   dummy text of   the   printing and typesetting industry.";
    int count = this.countWordsIteratively(content);

    Assertions.assertThat(count).isEqualTo(12);

    // 2. Stream์˜ ๋ฆฌ๋“€์Šค ์—ฐ์‚ฐ
    Stream<Character> stream = IntStream.range(0, content.length()).mapToObj(content::charAt);
    WordCounter reduce = stream.reduce(
        new WordCounter(0, true),
        WordCounter::accumulate,
        WordCounter::combine
    );

    Assertions.assertThat(reduce.getCounter()).isEqualTo(12);

    // 3. ๋ณ‘๋ ฌ๋กœ ์—ฐ์‚ฐ
    WordCounterSpliterator wordCounterSpliterator = new WordCounterSpliterator(content);
    Stream<Character> stream2 = StreamSupport.stream(wordCounterSpliterator, true); // ๋ณ‘๋ ฌ ์ŠคํŠธ๋ฆผ ์ƒ์„ฑ ์—ฌ๋ถ€ true
    WordCounter reduce2 = stream2.reduce(
        new WordCounter(0, true),
        WordCounter::accumulate,
        WordCounter::combine
    );

    Assertions.assertThat(reduce2.getCounter()).isEqualTo(12);
}

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