JAVA

쓰레드 동기화 (Synchronization)

도원좀비 2025. 3. 11. 21:06

1. 동기화가 필요한 이유 (Race Condition)

멀티 쓰레드 환경에서 여러 개의 쓰레드가 동시에 같은 변수나 데이터에 접근하면 값이 꼬이는 문제가 발생할 수 있다.

 

동기화가 없는 경우 (문제 발생)

더보기
class Counter {
    private int count = 0;

    public void increment() {
        count++; // 여러 쓰레드가 동시에 접근하면 값이 꼬일 가능성이 있음
    }

    public int getCount() {
        return count;
    }
}

public class RaceConditionExample {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("최종 카운트 값 (예상: 2000): " + counter.getCount());
    }
}

2. synchronized 키워드로 해결하기

문제를 해결하려면 synchronized 키워드로 메서드를 동기화

더보기
class SynchronizedCounter {
    private int count = 0;

    // synchronized를 사용하여 동시 접근 방지
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

public class SynchronizedExample {
    public static void main(String[] args) {
        SynchronizedCounter counter = new SynchronizedCounter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("최종 카운트 값 (예상: 2000): " + counter.getCount());
    }
}

3. synchronized 블록 사용하기

특정 코드 블록만 동기화하고 싶다면 synchronized 블록을 사용

class SynchronizedBlockCounter {
    private int count = 0;

    public void increment() {
        synchronized (this) { // 특정 부분만 동기화
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

4. volatile 키워드

volatile은 변수 값이 캐시에 저장되지 않고 항상 메모리에서 읽도록 보장하는 키워드

class VolatileExample {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public void runTask() {
        while (running) {
            // running이 변경될 때까지 실행됨
        }
        System.out.println("스레드 종료");
    }
}

volatile을 사용하면 여러 쓰레드가 값을 변경할 때 최신 값을 반영하도록 보장하지만, 동기화 기능은 없음
volatile은 간단한 플래그 값 변경에 적합하고, 복잡한 연산에는 synchronized를 사용해야 함


5. ReentrantLock (더 정교한 동기화)

synchronized 보다 더 유연한 동기화 기법

 

더보기
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockCounter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); //  락 획득
        try {
            count++;
        } finally {
            lock.unlock(); //  락 해제
        }
    }

    public int getCount() {
        return count;
    }
}

public class ReentrantLockExample {
    public static void main(String[] args) {
        LockCounter counter = new LockCounter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("최종 카운트 값 (예상: 2000): " + counter.getCount());
    }
}

ReentrantLock은 synchronized보다 더 세밀한 제어 가능
예를 들어, 타임아웃 기능, 락을 여러 번 걸고 해제하는 기능 등이 필요할 때 사용


정리 : 언제 어떤 동기화 기법을 사용해야 할까?

동기화 방법 특징 언제 사용?
synchronized 간단한 동기화, 코드가 직관적 일반적인 동기화
synchronized 블록 특정 코드만 동기화 가능 일부 코드만 보호할 때
volatile 변수의 최신 값 보장 (동기화 X) 단순한 플래그 값 변경
ReentrantLock 더 정교한 동기화 기능 제공 락을 세밀하게 관리할 때

'JAVA' 카테고리의 다른 글

CompletableFuture (비동기 처리)  (1) 2025.03.12
Executor 프레임워크  (0) 2025.03.12
쓰레드(Thread)  (0) 2025.03.11
Java Stream API 주요 메서드 정리  (2) 2025.02.28
자바 기본 다지기 최종  (2) 2025.02.27