๋ค์ด๊ฐ๋ฉฐ
๋ ๋ฆฝ ํ + ์ ์ฉ ์์ปค ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๋ ์๋น์ค์์ ์ข ๋ฃ ์ ํธ๊ฐ ์์ปค ์ค๋ ๋์ ์ ๋ฌ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. @PreDestroy์์ running = false๋ฅผ ๋ถ๋ช ํ ์ผ๋๋ฐ๋ ์์ปค ์ค๋ ๋๋ ๋ฃจํ๋ฅผ ๊ณ์ ๋๊ณ ์์๋ค.
private volatile boolean running = true;
ํด๊ฒฐ์ฑ ์ volatile ํ ํค์๋์์ง๋ง, ์ ์์ผ๋ฉด ์ ๋๋์ง๋ ์ค๋ช ํ๊ธฐ ์ด๋ ค์ ๋ค. ์ด ๊ธ์ ๋ฌธ์ ์ ์์ธ์ ํ์ ํ๊ณ ์ฌ๋ฐ๋ฅธ ๊ตฌํ์ ์ด๋ฅด๊ธฐ๊น์ง์ ๊ณผ์ ์ ๋ด์๋ค.
1. ๋ฌธ์ ๋ฐ์ - volatile ์์ด ์ด๋ค ์ผ์ด ๋ฒ์ด์ง๋๊ฐ
volatile ์์ด ์ข ๋ฃ ํ๋๊ทธ๋ฅผ ๊ตฌํํ๋ฉด ์ด๋ค ์ผ์ด ์๊ธฐ๋์ง ์ฝ๋๋ก ๋จผ์ ๋ณด์.
// volatile ์์
private boolean running = true;
// ์์ปค ์ค๋ ๋ (Core 1์์ ์คํ)
private void runWorker(String id, BlockingQueue<Runnable> queue) {
while (running) { // ← Core 1์ ์บ์์์ running ์ฝ๊ธฐ
Runnable task = queue.poll(500, TimeUnit.MILLISECONDS);
if (task == null) continue;
task.run();
Thread.sleep(1000);
}
drainQueue(id, queue);
}
// ๋ฉ์ธ ์ค๋ ๋ (Core 0์์ ์คํ)
@PreDestroy
public void shutdown() {
running = false; // ← Core 0์ ์บ์๋ฅผ false๋ก ๋ณ๊ฒฝ
// ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ์ ์ธ์ ๋ฐ์๋ ์ง ๋ณด์ฅ ์์
}
shutdown()์ด running = false๋ฅผ ์ผ์์๋ ์์ปค ์ค๋ ๋๊ฐ ๋ฃจํ๋ฅผ ๋น ์ ธ๋์ค์ง ์๋๋ค.
2. ์์ธ ๋ถ์ - CPU ์บ์์ ๋ฉ๋ชจ๋ฆฌ ๊ฐ์์ฑ
ํ๋ CPU๋ ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ(RAM)์ ์ง์ ์ ๊ทผํ๋ ๋์ L1/L2/L3 ์บ์๋ฅผ ๊ณ์ธต์ ์ผ๋ก ์ฌ์ฉํ๋ค. ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ์ ์๋ฐฑ ํด๋ญ์ด ๊ฑธ๋ฆฌ์ง๋ง L1 ์บ์๋ ์ ํด๋ญ์ด๋ฉด ์ถฉ๋ถํ๊ธฐ ๋๋ฌธ์ด๋ค.

Java Memory Model(JMM) ์ ์ฑ๋ฅ์ ์ํด ๊ฐ ์ค๋ ๋๊ฐ ๋ณ์์ ๋ก์ปฌ ๋ณต์ฌ๋ณธ(CPU ์บ์ ๋๋ ๋ ์ง์คํฐ)์ ์ฌ์ฉํ๋๋ก ํ์ฉํ๋ค. ๋ฐ๋ผ์ ํ ์ค๋ ๋๊ฐ ๋ณ์๋ฅผ ๋ณ๊ฒฝํด๋ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ทธ ๋ณ๊ฒฝ์ ์ธ์ ๋ณผ์ง ๋ณด์ฅํ์ง ์๋๋ค.
์ด๊ฒ์ด ๊ฐ์์ฑ(Visibility) ๋ฌธ์ ๋ค. shutdown()์ด running = false๋ฅผ ์จ๋ ๋ณ๊ฒฝ์ด ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ์ ์ฆ์ flush๋์ง ์์ ์ ์๊ณ , ์ค๋ น flush๋๋๋ผ๋ Core 1์ ์บ์๊ฐ ๊ฐฑ์ ๋์ง ์์ผ๋ฉด ์์ปค ์ค๋ ๋๋ ์์ํ running == true๋ฅผ ๋ณด๊ฒ ๋๋ค.
3. ํด๊ฒฐ์ฑ - volatile ์ ์ธ
private volatile boolean running = true;
volatile์ ์ ์ธํ๋ฉด JMM์ ๋ ๊ฐ์ง๋ฅผ ๋ณด์ฅํ๋ค.
3-1. ๊ฐ์์ฑ ๋ณด์ฅ (Visibility)
volatile ๋ณ์์ ๋ํ ์ฐ๊ธฐ๋ ์ฆ์ ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ์ flush๋๊ณ , ์ฝ๊ธฐ๋ ํญ์ ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ์์ ์ง์ ์ฝ๋๋ค. CPU ์บ์๋ฅผ ์ฐํํ๋ฏ๋ก ๋ชจ๋ ์ค๋ ๋๊ฐ ์ต์ ๊ฐ์ ๋ณธ๋ค.

3-2. ๋ช ๋ น์ด ์ฌ์ ๋ ฌ ๋ฐฉ์ง (Happens-Before)
JMM์ volatile ์ฐ๊ธฐ์ ์ฝ๊ธฐ ์ฌ์ด์ happens-before ๊ด๊ณ๋ฅผ ๋ณด์ฅํ๋ค.
volatile ๋ณ์์ ๋ํ ์ฐ๊ธฐ๋ ์ดํ ๊ฐ์ ๋ณ์๋ฅผ ์ฝ๋ ๋ชจ๋ ์์ ๋ณด๋ค happens-before ํ๋ค.
์ด ๋ง์ running = false ์ด์ ์ ์ํ๋ ๋ชจ๋ ์ฐ๊ธฐ ์์ ์ด running์ ์ฝ๋ ์ค๋ ๋์๋ ๋ณด์ฅ๋๋ค๋ ์๋ฏธ๋ค.

4. ์ฌ๋ฐ๋ฅธ ๊ตฌํ - ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ปค ์ค๋ ๋ ์ข ๋ฃ
๋ ๋ฆฝ์ ์ธ ์์ ํ์ ์ ์ฉ ์์ปค ์ค๋ ๋๋ฅผ ์ด์ํ๋ ๊ตฌ์กฐ๋ค.
// volatile ์์
private volatile boolean running = true; // ← ํต์ฌ
// ์์ปค ์ค๋ ๋ (Core 1์์ ์คํ)
private void runWorker(String id, BlockingQueue<Runnable> queue) {
while (running) { // ← ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ์์ ๋งค๋ฒ ์ฝ๊ธฐ
Runnable task = queue.poll(500, TimeUnit.MILLISECONDS);
if (task == null) continue;
task.run();
Thread.sleep(1000);
}
drainQueue(id, queue); // ์์ฌ ์์
์ฒ๋ฆฌ
}
// ๋ฉ์ธ ์ค๋ ๋ (Core 0์์ ์คํ)
@PreDestroy
public void shutdown() {
running = false; // ← ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ์ ์ฆ์ ๋ฐ์
executors.values().forEach(ExecutorService::shutdown);
// ... awaitTermination
}
- ์ฐ๋ ์ค๋ ๋: @PreDestroy๋ฅผ ํธ์ถํ๋ ๋ฉ์ธ(Spring ์ปจํ ์คํธ) ์ค๋ ๋
- ์ฝ๋ ์ค๋ ๋: runWorker()๋ฅผ ์คํ ์ค์ธ ์์ปค ์ค๋ ๋๋ค (id๋น 1๊ฐ)
์๋ก ๋ค๋ฅธ ์ค๋ ๋์ด๋ฏ๋ก, volatile ์์ด๋ ์์ปค ์ค๋ ๋๊ฐ running = false๋ฅผ ์์ํ ๋ชป ๋ณผ ์๋ ์๋ค.

5. ์ฃผ์์ฌํญ - volatile์ด ํด๊ฒฐํ์ง ๋ชปํ๋ ๊ฒ
volatile์ ๊ฐ์์ฑ๊ณผ happens-before๋ฅผ ๋ณด์ฅํ์ง๋ง, ๋ณตํฉ ์ฐ์ฐ์ ์์์ฑ์ ๋ณด์ฅํ์ง ์๋๋ค.
private volatile int count = 0;
// ์ค๋ ๋ A์ B๊ฐ ๋์์
count++;
count++๋ ์ค์ ๋ก ์ธ ๋จ๊ณ๋ค: ์ฝ๊ธฐ → ์ฆ๊ฐ → ์ฐ๊ธฐ.

๋ ์ค๋ ๋๊ฐ ๋์์ ์ฝ๊ธฐ ๋จ๊ณ๋ฅผ ํต๊ณผํ๋ฉด ๋ ๋ค ๊ฐ์ ๊ฐ์ ์ฝ๊ณ , ๋ ๋ค +1์ ํ ๋ค ๊ฐ์ ๊ฐ์ ๋ ๋ฒ ์ด๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฆ๊ฐ๊ฐ ํ ๋ฒ๋ฐ์ ๋ฐ์๋์ง ์๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์ AtomicInteger๋ฅผ ์จ์ผ ํ๋ค.
private final AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // CAS ์ฐ์ฐ์ผ๋ก ์์์ฑ ๋ณด์ฅ
์ฐธ๊ณ
- Java Language Specification — §17.4 Memory Model
- CPU ์บ์์ ๊ฐ์์ฑ ๋ฌธ์ - JMM์ด ์ค๋ ๋๋ณ ๋ก์ปฌ ๋ณต์ฌ๋ณธ์ ํ์ฉ
- volatile ์ ๋ ๊ฐ์ง ๋ณด์ฅ - ๊ฐ์์ฑ(flush/read ๋ณด์ฅ)๊ณผ happens-before ๊ด๊ณ