동시성

여러 작업이 동시에 실행되는 것처럼 보이도록 처리하는 개념입니다.

동시성 vs 병렬성

동시성 (Concurrency)
→ 하나의 코어가 여러 작업을 번갈아 처리
→ 동시에 실행되는 것처럼 보이지만 실제론 교차 실행

CPU: [작업A] [작업B] [작업A] [작업B]

병렬성 (Parallelism)
→ 여러 코어가 여러 작업을 진짜 동시에 처리

CPU1: [작업A] [작업A]
CPU2: [작업B] [작업B]

동시성 문제

1. 경쟁 조건 (Race Condition)

// 두 스레드가 동시에 count++ 실행 시
class Counter {
    private int count = 0;

    public void increment() {
        count++;  // X 원자적 연산이 아님
        // 내부적으로: read → modify → write 3단계
        // 스레드 간 끼어들기 가능 → 결과 예측 불가
    }
}

// 예상: 1000, 실제: 997 (스레드 간 덮어쓰기 발생)

2. 교착 상태 (Deadlock)

// 스레드 A: lock1 잡고 lock2 기다림
// 스레드 B: lock2 잡고 lock1 기다림
// → 서로 기다리며 영원히 멈춤 X

synchronized(lock1) {
    synchronized(lock2) { ... }  // Thread A
}

synchronized(lock2) {
    synchronized(lock1) { ... }  // Thread B
}

해결 방법

synchronized

class Counter {
    private int count = 0;

    // 한 번에 하나의 스레드만 실행 가능
    public synchronized void increment() {
        count++;  // O 안전
    }
}

AtomicInteger

// synchronized보다 가벼운 원자적 연산
class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();  // O 원자적 연산 보장
    }
}

volatile

// 변수를 CPU 캐시가 아닌 메인 메모리에서 직접 읽기/쓰기
class Flag {
    private volatile boolean running = true;  // 스레드 간 최신값 보장

    public void stop() { running = false; }
    public boolean isRunning() { return running; }
}

동시성 도구 비교

도구특징사용 상황
synchronized간단, 무거움복잡한 임계 구역
AtomicInteger가벼움, 단순 연산카운터, 플래그
volatile가시성만 보장단순 읽기/쓰기
ReentrantLock유연한 락 제어타임아웃, 조건 필요 시
ConcurrentHashMap스레드 안전 Map동시 Map 접근

Java에서의 스레드

// 스레드 생성
Thread thread = new Thread(() -> {
    System.out.println("별도 스레드 실행");
});
thread.start();

// ExecutorService (실무 권장)
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
    System.out.println("스레드풀에서 실행");
});
executor.shutdown();

동시성의 핵심은 "공유 자원에 여러 스레드가 동시에 접근할 때 발생하는 문제를 어떻게 제어하느냐" 입니다. 무조건 synchronized를 남발하면 성능이 저하되므로, 상황에 맞는 도구 선택이 중요합니다.

CSLOG에서 보기