김영한의 실전 자바 - 고급 1편(총 정리)

2026. 1. 26. 01:52·김영한의 실전 자바 - 고급 1편

힙(Heap)과 스택(Stack)

힙(Heap)

  • 동적 할당 객체(대부분의 new로 생성된 객체)가 저장되는 메모리 영역
  • 프로세스(= JVM) 단위로 공유되므로 여러 스레드가 같은 힙을 함께 사용
  • 객체는 GC 대상이 되며, 참조가 끊기면 회수 가능

스택(Stack)

  • 함수(메서드) 호출 단위로 프레임이 쌓이는 영역
  • 스레드마다 별도의 스택을 가짐(스레드 로컬)
  • 주로 저장되는 것들
    • 지역 변수, 매개변수, 리턴 주소, 임시 값 등

전역 변수는 어디에 저장되나?

  • Java 기준으로 “전역 변수”에 가까운 것은 클래스 변수(static)
  • static 필드, 상수 등은 JVM의 메서드 영역(Method Area / Metaspace)에 올라가며 모든 스레드가 공유
  • (참고) 네이티브/저수준 관점에서는 “데이터 섹션” 같은 표현을 쓰지만, Java/JVM에서는 보통 메서드 영역으로 설명하는 게 정확함

Thread.start() vs run() 차이

start()

  • JVM이 새 스레드(실행 흐름)를 만들고, 그 스레드에서 run()을 호출
  • 즉, 멀티스레딩 환경이 구성됨

run()

  • 단순히 일반 메서드 호출
  • 호출한 스레드(보통 main 스레드)에서 그대로 실행됨 → 새 스레드가 생성되지 않음
Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));

t.run();   // main에서 실행됨
t.start(); // 별도 스레드에서 실행됨

스레드 상태 (Thread State)

  • NEW: 생성만 되었고 아직 start() 안 함
  • RUNNABLE: 실행 중이거나 실행 가능한 상태(실제로는 OS 스케줄링 대기 포함)
  • BLOCKED: synchronized 모니터 락을 얻기 위해 진입 대기 중
  • WAITING: 시간 제한 없이 대기 (Object.wait(), Thread.join() 등)
  • TIMED_WAITING: 시간 제한 대기 (sleep, wait(timeout), join(timeout) 등)
  • TERMINATED: 실행 완료

join() 함수

  • 특정 스레드가 끝날 때까지 기다릴 때 사용 (busy-wait / sleep 반복보다 효율적)
  • 호출한 스레드는 WAITING 상태가 됨
  • 무한 대기 문제는 join(timeout)으로 완화 → TIMED_WAITING
Thread t = new Thread(task);
t.start();

t.join();        // 끝날 때까지 대기 (WAITING)
t.join(1000L);   // 최대 1초만 대기 (TIMED_WAITING)

volatile이 뭐고 왜 쓰는지

핵심 목적: 가시성(visibility) 보장

  • 여러 스레드가 공유 변수를 볼 때, 각자 CPU 캐시/레지스터에 의해 최신 값이 안 보일 수 있음
  • volatile은
    • 쓰기(write): 다른 스레드가 볼 수 있도록 “즉시” 메모리에 반영되도록
    • 읽기(read): 최신 값을 메모리에서 가져오도록
  • 또한 volatile write → read 사이에 happens-before 관계를 만들어 순서/가시성을 보장

주의: 원자성(atomicity)을 보장하지는 않음

  • volatile int x; x++는 원자적이지 않음 (읽기-증가-쓰기의 3단계)

적합한 경우

  • “플래그” 같은 단순 상태 공유(종료 신호 등)
class StopFlag {
  private volatile boolean stop = false;
  void requestStop() { stop = true; }
  void work() {
    while (!stop) { /* do work */ }
  }
}

withdraw() 같은 출금 함수의 동시성 문제를 심플하게 해결하려면?

1) synchronized로 임계 구역 보호

  • 함수 전체에 걸거나, 문제되는 부분만 블록으로 최소화 가능
class Account {
  private int balance = 1000;

  public synchronized boolean withdraw(int amount) {
    if (balance < amount) return false;
    balance -= amount;
    return true;
  }

  // 또는
  public boolean withdraw2(int amount) {
    synchronized (this) {
      if (balance < amount) return false;
      balance -= amount;
      return true;
    }
  }
}

위 방법(synchronized)의 단점과 해결 방향

단점

  • 경쟁이 심하면 BLOCKED가 늘어나고 성능 저하
  • 락을 얻기까지 무한 대기 가능
  • 락 획득 대기 중에는 제어 옵션이 제한적(타임아웃, 인터럽트 대응 등)

대안 1) LockSupport 기반 대기 제어

  • park()/parkNanos()로 대기 시간 제어 가능
  • park는 WAITING/TIMED_WAITING 형태로 대기하며, 설계에 따라 interrupt 처리를 유연하게 구성 가능
LockSupport.parkNanos(1_000_000L); // 1ms 대기

대안 2) ReentrantLock (실무에서 가장 흔한 Lock 구현체)

  • tryLock() / tryLock(timeout)로 무한 대기 회피
  • lockInterruptibly()로 인터럽트 반응 가능
  • Condition으로 wait/notify보다 명확한 조건 대기 구현
ReentrantLock lock = new ReentrantLock();

boolean ok = lock.tryLock(100, TimeUnit.MILLISECONDS);
if (ok) {
  try { /* critical section */ }
  finally { lock.unlock(); }
}

CAS란?

  • Compare-And-Swap: 메모리 값이 예상값과 같으면 새 값으로 바꾸는 하드웨어 원자 연산
  • 락 없이도 원자적 갱신이 가능해서 성능이 유리한 경우가 많음(특히 경합이 낮을 때)
  • 단, CAS는 재시도 루프가 필요해 경합이 높으면 스핀 비용이 커질 수 있음

Java에서의 예: AtomicInteger

AtomicInteger v = new AtomicInteger(0);
v.incrementAndGet(); // 내부적으로 CAS 기반

Future 객체

  • 비동기 작업 결과를 “나중에” 받기 위한 핸들
  • 주요 기능
    • get() : 결과 대기(블로킹)
    • get(timeout) : 시간 제한 대기
    • cancel(true) : 취소 시도(인터럽트 전달 가능)
    • isDone(), isCancelled()
Future<Integer> f = executor.submit(() -> 1 + 2);
Integer result = f.get(1, TimeUnit.SECONDS);

참고: Future는 콜백/조합이 불편해서, Java 8+에서는 보통 CompletableFuture를 더 자주 사용함.


ThreadPoolExecutor / ExecutorService

왜 쓰나?

  • 스레드 생성/종료 비용이 큼 → 스레드를 재사용해서 성능 안정화
  • 작업 제출과 실행을 분리하고, 큐/정책으로 부하를 제어

핵심 구성(ThreadPoolExecutor)

  • corePoolSize: 기본 유지 스레드 수
  • maximumPoolSize: 최대 스레드 수
  • workQueue: 작업 큐(대기열)
  • keepAliveTime: 초과 스레드 유지 시간
  • RejectedExecutionHandler: 포화 시 거절 정책
ExecutorService pool =
  new ThreadPoolExecutor(
    4, 8,
    30, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
  );

여러 가지 풀 전략

1) Fixed Thread Pool

  • 고정된 스레드 수
  • CPU 바운드 작업에 자주 사용
Executors.newFixedThreadPool(n);

2) Cached Thread Pool

  • 필요 시 스레드 생성, 유휴 시 회수
  • 짧은 작업이 많고 폭발적 트래픽에 유리하지만, 과도한 스레드 증가 위험
Executors.newCachedThreadPool();

3) Single Thread Executor

  • 단일 스레드로 순차 처리(작업 순서 보장)
Executors.newSingleThreadExecutor();

4) Scheduled Thread Pool

  • 지연/주기 작업
Executors.newScheduledThreadPool(n);

5) Work-Stealing / ForkJoinPool

  • 작업을 작은 단위로 쪼개 병렬 처리(분할 정복)
  • CPU 바운드에 적합
Executors.newWorkStealingPool();

큐/정책 선택 포인트(실무에서 중요)

  • LinkedBlockingQueue(bounded 권장): 큐가 길어질 수 있음 → 메모리/지연 증가 가능
  • SynchronousQueue: 큐 없이 즉시 핸드오프 → 급격히 스레드가 늘 수 있음
  • 거절 정책:
    • AbortPolicy(기본): 예외
    • CallerRunsPolicy: 호출자 스레드가 실행(백프레셔)
    • DiscardPolicy/DiscardOldestPolicy: 유실 가능성

원하면 위 내용을 “한 페이지짜리 블로그 포스트 형태(서론/요약/실무 팁 포함)”로 재편집해서 더 깔끔한 최종본으로도 정리해줄 수 있음.

'김영한의 실전 자바 - 고급 1편' 카테고리의 다른 글

김영한의 실전 자바 - 고급 2편(총 정리)  (0) 2026.01.26
김영한의 실전 자바 - 고급 1편(Executor)  (0) 2025.01.13
김영한의 실전 자바 - 고급 1편(동시성 컬렉션)  (0) 2025.01.11
김영한의 실전 자바 - 고급 1편(CAS)  (1) 2025.01.11
김영한의 실전 자바 - 고급 1편(생산자 소비자 문제: Object - wait/notify, ReentarantLock - await/signal, BlockingQueue)  (1) 2025.01.08
'김영한의 실전 자바 - 고급 1편' 카테고리의 다른 글
  • 김영한의 실전 자바 - 고급 2편(총 정리)
  • 김영한의 실전 자바 - 고급 1편(Executor)
  • 김영한의 실전 자바 - 고급 1편(동시성 컬렉션)
  • 김영한의 실전 자바 - 고급 1편(CAS)
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (242)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (118)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      락
      프록시 팩토리
      reentarantlock
      단방향 맵핑
      jdk 동적 프록시
      양방향 맵핑
      JPQL
      Target
      프록시
      Thread
      김영한
      gesingleresult
      log trace
      hibernate5module
      빈 후처리기
      cglib
      버퍼
      @discriminatorvalue
      자바
      WAS
      requset scope
      @discriminatorcolumn
      jpq
      고급
      조회 성능 최적화
      페치 조인
      스레드
      @args
      @within
      typequery
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    김영한의 실전 자바 - 고급 1편(총 정리)
    상단으로

    티스토리툴바