공공부하자개발 · 영어 학습 노트
자바
고급모던 자바와 성능0/10 완료
  • 01제네릭과 와일드카드
  • 02멀티스레드와 동기화
  • 03람다식과 함수형 인터페이스
  • 04Stream API와 병렬 처리
  • 05Optional로 NPE 방지
  • 06메서드 활용 패턴 (고급)
  • 07Java 21 모던 문법
  • 08어노테이션·리플렉션·동적 프록시
  • 09CompletableFuture 심화와 가상 스레드 실전
  • 10JVM 메모리·GC·OOM 진단
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 고급 › 02 / 10

멀티스레드와 동기화

섹션 7진행 0 / 10
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

6. 연습 문제

문제 1

RequestCounter 클래스를 만드세요. hit(String path) 는 경로별 호출 수를 세고, snapshot() 은 Map<String, Integer> 를 반환합니다. 8개 스레드가 각각 /a, /b 를 10,000번씩 호출한 뒤 결과가 정확히 80,000 / 80,000 이 되도록 하세요. ConcurrentHashMap.merge 를 사용하세요.

정답 보기
java
import java.util.*;
import java.util.concurrent.*;

public class Ex1 {
    static class RequestCounter {
        private final ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
        void hit(String path) { counts.merge(path, 1, Integer::sum); }   // merge 는 원자적
        Map<String, Integer> snapshot() { return new TreeMap<>(counts); }
    }

    public static void main(String[] args) throws Exception {
        RequestCounter c = new RequestCounter();
        try (var pool = Executors.newFixedThreadPool(8)) {
            for (int t = 0; t < 8; t++)
                pool.submit(() -> { for (int i = 0; i < 10_000; i++) { c.hit("/a"); c.hit("/b"); } });
        }
        System.out.println(c.snapshot());
    }
}
// 출력:
// {/a=80000, /b=80000}

문제 2

외부 API 호출을 흉내 내는 String fetch(int id) (200ms sleep 후 "item-" + id 반환) 를 20번 호출해야 합니다. (1) 순차 호출 (2) 가상 스레드로 병렬 호출 두 가지를 구현하고, 각각의 소요 시간이 약 4초 vs 약 0.2초임을 출력하세요. 결과 리스트는 id 순서를 유지해야 합니다.

정답 보기
java
import java.util.*;
import java.util.concurrent.*;

public class Ex2 {
    static String fetch(int id) {
        try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        return "item-" + id;
    }

    public static void main(String[] args) throws Exception {
        long t0 = System.currentTimeMillis();
        List<String> seq = new ArrayList<>();
        for (int i = 0; i < 20; i++) seq.add(fetch(i));
        System.out.println("sequential ~" + (System.currentTimeMillis() - t0) / 1000 + "s, " + seq.size());

        t0 = System.currentTimeMillis();
        List<Future<String>> fs = new ArrayList<>();
        try (var ex = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 20; i++) { int id = i; fs.add(ex.submit(() -> fetch(id))); }
        }
        List<String> par = new ArrayList<>();
        for (Future<String> f : fs) par.add(f.get());          // submit 순서 = id 순서
        System.out.println("parallel ~" + (System.currentTimeMillis() - t0) / 1000 + "s, " + par.get(19));
    }
}
// 출력:
// sequential ~4s, 20
// parallel ~0s, item-19

문제 3

BoundedBuffer<T> 를 ReentrantLock 과 두 개의 Condition(notFull, notEmpty) 으로 구현하세요. put 은 가득 차면 대기, take 는 비면 대기합니다. 용량 2 버퍼에 생산자 1개가 1~5 를 넣고 소비자 1개가 꺼내 합계 15 를 출력하세요.

정답 보기
java
import java.util.*;
import java.util.concurrent.locks.*;

public class Ex3 {
    static class BoundedBuffer<T> {
        private final Deque<T> items = new ArrayDeque<>();
        private final int capacity;
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition notFull = lock.newCondition();
        private final Condition notEmpty = lock.newCondition();

        BoundedBuffer(int capacity) { this.capacity = capacity; }

        void put(T t) throws InterruptedException {
            lock.lock();
            try {
                while (items.size() == capacity) notFull.await();   // while: 허위 깨어남(spurious wakeup) 대비
                items.addLast(t);
                notEmpty.signal();
            } finally { lock.unlock(); }
        }

        T take() throws InterruptedException {
            lock.lock();
            try {
                while (items.isEmpty()) notEmpty.await();
                T t = items.pollFirst();
                notFull.signal();
                return t;
            } finally { lock.unlock(); }
        }
    }

    public static void main(String[] args) throws Exception {
        BoundedBuffer<Integer> buf = new BoundedBuffer<>(2);
        int[] sum = {0};
        Thread producer = new Thread(() -> { try { for (int i = 1; i <= 5; i++) buf.put(i); } catch (InterruptedException e) {} });
        Thread consumer = new Thread(() -> { try { for (int i = 0; i < 5; i++) sum[0] += buf.take(); } catch (InterruptedException e) {} });
        producer.start(); consumer.start();
        producer.join(); consumer.join();
        System.out.println("sum = " + sum[0]);
    }
}
// 출력:
// sum = 15
연습 문제
  • 문제 1
  • 문제 2
  • 문제 3
이전 섹션5 자주 하는 실수 (Tip)6 / 7다음 섹션7 정리