Java 스레드 아키텍처와 동기화: CPU부터 JVM까지
Java 코드는 바이트코드로 컴파일되며, JVM이 이를 프로세서의 기계어 명령어로 변환합니다. 각 CPU 코어는 명령어를 순차적으로 실행합니다: 레지스터에서 비트 시프트 같은 간단한 연산은 한 클럭 사이클에 처리됩니다. 레지스터는 숫자, 주소, 데이터를 저장하는 초고속 메모리 셀입니다.
현대 프로세서는 슈퍼스칼라 아키텍처를 사용합니다. 하이퍼스레딩은 단일 하드웨어 스레드가 한 사이클에 2~6개의 명령어를 실행하게 해, 빠른 컨텍스트 스위칭으로 한 코어당 여러 논리 스레드가 있는 듯한 착각을 줍니다.
동시에 실행되는 스레드 수는 코어 수에 제한됩니다. 스레드들은 객체를 위한 힙 메모리를 공유하지만, 각자 로컬 변수와 객체 참조를 위한 스택을 가집니다.
레이스 컨디션과 메모리 구조
레이스 컨디션은 여러 스레드가 동시에 같은 힙 객체를 읽고 수정할 때 발생합니다. 결과는 읽기-수정-쓰기 작업 순서에 따라 예측 불가능합니다.
코드는 로컬에서 부하 없이 잘 동작하지만, 프로덕션 환경의 멀티스레딩에서 불규칙적으로 충돌이 발생합니다. OS 프로세스는 공유 메모리를 할당하며, 그 안의 스레드들은 자원을 놓고 경쟁하는 가벼운 실행 단위입니다.
동기화 메커니즘: wait/notify와 모니터
모든 Java 객체는 내장 모니터(락)를 가집니다. synchronized 블록은 객체의 모니터를 획득해 다음을 보장합니다:
- 상호 배제: 블록 안 코드는 한 스레드만 실행.
- 가시성: 블록 종료 후 변경 사항이 다른 스레드에 보임 (
volatile지원 필요).
모니터는 다음을 포함합니다:
- Entry Set: 획득 대기 스레드 큐.
- Wait Set: 대기 상태 스레드.
wait(), notify(), notifyAll()은 같은 객체의 synchronized 블록 안에서만 동작—아니면 IllegalMonitorStateException 발생.
wait(): 스레드를 Wait Set으로 이동.notify(): 랜덤 스레드 깨움 (신호 손실 위험).notifyAll(): 모든 스레드 깨움 (권장).
정적 synchronized 메서드는 클래스 모니터(Class)를 락하며, 인스턴스 메서드(this)와 충돌 없음.
고급 락킹: ReentrantLock과 Semaphore
더 세밀한 제어를 위해 java.util.concurrent.locks.ReentrantLock 사용:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 크리티컬 섹션
} finally {
lock.unlock();
}
주요 메서드:
tryLock(): 대기 없이 획득 (실패 시 false 반환).tryLock(timeout, unit): 타임아웃 대기.lockInterruptibly(): 인터럽트 가능 획득.new ReentrantLock(true): 공정 락 (FIFO 순서, 느림).
Semaphore는 접근 제한:
Semaphore sem = new Semaphore(3); // 3개 허가
sem.acquire(); // 획득
sem.release(); // 해제
멀티스레딩 함정: 데드락과 라이브락
데드락: 순환 락 의존. 스레드 A가 lock1 보유하고 lock2 대기; 스레드 B가 반대. 시스템 멈춤.
피하는 법:
- 고정 순서로 락.
- 타임아웃 사용.
- 중첩 락 최소화.
라이브락: 스레드 바쁘지만 진척 없음. 예: 두 스레드가 서로 양보하며 루프 돌며 CPU 폭증.
스레드의 동기 vs 비동기
동기 메서드는 I/O(예: DB 쿼리)에서 스레드 블로킹: 응답까지 스레드 유휴.
비동기는 CompletableFuture 사용—지연 결과 컨테이너(대기/완료/실패).
연산 체인:
thenApply(): 결과 변환.thenCompose(): 다른 Future와 조합.thenAccept(): 반환 없이 소비.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "result")
.thenApply(s -> s.toUpperCase())
.thenAccept(System.out::println);
이로 스레드가 다른 작업에 자유로워 멀티스레드 환경 처리량 증가.
주요 요약
- 코어당 하드웨어 스레드 하나; 하이퍼스레딩으로 다수 시뮬레이션.
- 레이스 컨디션은 공유 힙에서; 동기화로 상호 배제와 가시성 보장.
- 신호 손실 피하려
notifyAll()선호. - ReentrantLock은 타임아웃, 인터럽트, 공정성으로
synchronized확장. - CompletableFuture로 블로킹 없이 비동기 파이프라인.
— Editorial Team
아직 댓글이 없습니다.