serversim
학습시뮬레이터템플릿가격
…
…
메뉴 열기
학습↗시뮬레이터↗템플릿↗가격↗
내 Documents문서
전체 목차/2장/2.15 / 40

2.1 · 프로세스와 동시성

CPU에서 운영 체제, 프로세스, 스레드까지

1코어 CPU가 Java의 sleep·blocking 코드를 만났을 때, 거기 머무르지 않고 어떻게 다른 일을 할까요?

CPU는 sleep의 뜻을 모릅니다. JVM이 대기를 요청하고, 커널이 스레드를 실행 후보에서 빼고, 스케줄러가 다음 runnable 스레드를 고릅니다.

먼저 생활 비유

창구 하나와 번호표 세 장

직원이 서류가 도착할 때까지 한 손님 앞에서 가만히 서 있지 않습니다. 그 손님의 서류를 대기함에 넣고 다음 번호를 부릅니다. 서류가 오면 다시 대기 번호에 올리지만, 바로 창구를 차지하는 것은 아닙니다.

컴퓨터 예제로 바꾸기

sleep하는 A와 계산하는 B

Thread A: Thread.sleep(1000);
Thread B: calculateNextJob();

A의 호출 스택과 다음 실행 위치는 메모리에 보관합니다. A가 타이머 대기에 있는 동안 한 개 코어에는 B의 PC와 레지스터 문맥을 올려 B의 명령을 실행합니다.

1 CORE MECHANISM LAB1 / 6

CPU는 기다리는 Java 스레드에 계속 머무를까?

코어는 하나만 둡니다. 아래 네 경우를 바꿔 보면서 실행 중, 실행 가능, 대기를 분리해 보세요.

먼저 예상해 보기

sleep(1000)을 만나면 CPU도 1초 동안 멈출까?

정답아니요. A 스레드만 타이머 대기열로 빠지고, 한 개 코어는 B나 C를 실행합니다.

변화 1

A가 한 개뿐인 코어를 사용 중

지금 이 찰나에는 A의 명령만 실행됩니다. B와 C는 동시에 실행되는 것이 아니라 실행 가능 큐에서 차례를 기다립니다.

01Java 코드와 스레드 상태
A · 주문 처리 스레드
1log("A 시작")2Thread.sleep(1000)3log("A 다시 실행")
B · 다른 요청 스레드
1while (hasJob()) {2  calculateNextJob()3}
스레드Java 상태OS 상태
ARUNNABLERUNNING
BRUNNABLERUNNABLE
CRUNNABLERUNNABLE

* Java의 상태 이름과 OS 스레드 상태는 1:1로 같지 않습니다.

02실제로 실행권이 이동하는 곳
JVM · 사용자 영역Java 스레드 문맥호출 스택 · PC · 지역 변수는 메모리에 보관
OS KERNEL스케줄러와 대기 구조
RUN QUEUE지금 실행해도 되는 후보
TIMER QUEUE타이머 대기깨울 시각을 기록
I/O WAIT QUEUE장치 대기현재 사용하지 않음
SCHEDULER다음 runnable 고르기
HARDWARECPU · 1 core
Core 0 · 한 자리뿐RUNNINGlog("A 시작")
A주문 A
B주문 B
C화면 갱신
지금 이동·변화Core 0에 A의 PC·레지스터가 올라와 있다

한 순간에는 A 하나만 실행됩니다.

그래서 누가 판단할까?

한 명이 전부 결정하지 않습니다.

각 층은 자기에게 보이는 정보만 처리합니다. 현재 단계에서 주도한 층을 밝게 표시했습니다.

  1. 01Java 코드

    sleep·read·락·반복 같은 행동을 요청

  2. 02JVM

    Java 상태·모니터를 관리하고 네이티브 기능 호출

  3. 03OS 커널

    타이머·I/O 대기와 runnable 상태를 관리

  4. 04스케줄러

    runnable 후보 중 다음 실행 흐름을 선택

  5. 05CPU Core 0

    선택된 문맥의 기계 명령만 실행

시간은 왼쪽에서 오른쪽으로한 칸에 실행 중인 스레드는 최대 하나

이 그림은 이해를 위해 실제 나노초·밀리초 규모의 변화를 느리게 펼친 것입니다. 문맥 교환에는 비용이 있고, 정확한 선택 시점과 순서는 OS 정책·우선순위·부하에 따라 달라집니다.

이 그림의 기준

Java 플랫폼 스레드

전통적인 Java 스레드는 OS 스레드와 거의 1:1로 연결되므로 blocking 동안 그 OS 스레드가 잠듭니다. 하지만 CPU 코어는 다른 OS 스레드에 줄 수 있습니다.

Java 가상 스레드는?

기다릴 때 carrier에서 내려올 수 있음

지원되는 blocking 작업에서는 가상 스레드의 문맥을 메모리에 두고 carrier 플랫폼 스레드가 다른 가상 스레드를 실행합니다. 이후 별도 실험에서 플랫폼/가상 스레드를 나란히 비교합니다.

여기서 많이 헷갈리는 세 가지

“기다린다”는 말 하나에 서로 다른 동작이 숨어 있습니다.

다른 runnable이 0개라면코어는 idle로 간다

커널의 idle 흐름이 선택되고 CPU는 인터럽트를 기다리는 저전력 상태로 들어갈 수 있습니다. sleep 중인 A를 억지로 실행하지 않습니다.

sleep 안에서 락을 잡고 있다면락은 그대로 보유한다

Thread.sleep은 현재 스레드의 실행만 멈추며 보유한 모니터를 놓지 않습니다. 그래서 다른 스레드가 그 락 앞에서 막힐 수 있습니다.

Object.wait라면해당 모니터를 놓고 잔다

wait()는 객체의 wait set에 들어가며 그 객체의 모니터를 내려놓습니다. 깨어난 뒤에도 락을 다시 얻어야 반환할 수 있습니다.

검증 기준Java Thread.State 공식 문서 ↗Thread.sleep 공식 문서 ↗Object.wait 공식 문서 ↗Linux EEVDF 스케줄러 문서 ↗OpenJDK Virtual Threads ↗
한 층 더 내려가기: 선택된 스레드의 명령이 PC·IR·레지스터에서 실행되는 과정 →

처음 보는 용어 두 개

RUNNABLE
실행 중이거나 CPU 차례를 받을 수 있는 상태. Java에서는 둘을 하나의 상태 이름으로 표현합니다.
문맥 교환
현재 스레드의 PC·레지스터 값을 저장하고 다음 스레드의 값을 CPU에 복원하는 일

이 절을 다 읽었다면

대기는 코드를 없애는 것이 아니라 그 스레드의 문맥을 메모리에 보관하고 실행 후보에서 잠시 빼는 일입니다. 코어는 runnable인 다른 스레드를 실행합니다.

← 이전 절

1.4 왜 컴퓨터 과학에서는 추상화가 중요할까?

다음 절 →

2.2 스레드는 무엇을 공유하고 무엇을 따로 가질까?