[Java] 자바 synchronized (멀티스레드/동기화)

Posted by nkjok
2025. 3. 22. 20:45 낙서장[1]/91. Java
반응형

자바에서 synchronized 키워드는 멀티스레드 환경에서 동기화를 위해 사용되는 중요한 기능입니다. 이 포스팅에서는 synchronized의 개념, 사용 방법, 장단점, 그리고 예시를 통해 상세하게 설명하겠습니다.

1. synchronized의 개념

동기화란?

동기화는 여러 스레드가 동시에 접근할 수 있는 공유 자원에 대해 한 번에 하나의 스레드만 접근할 수 있도록 제어하는 것을 의미합니다. 이를 통해 데이터의 일관성을 유지하고, 경쟁 상태(race condition)를 방지할 수 있습니다.

synchronized 키워드

자바에서 synchronized 키워드는 메서드나 코드 블록 앞에 사용되어 해당 영역을 임계 영역(critical section)으로 지정합니다. 임계 영역은 한 번에 하나의 스레드만 접근할 수 있는 코드 영역을 의미합니다.

2. synchronized의 사용 방법

메서드 동기화

메서드 선언부에 synchronized 키워드를 붙여서 해당 메서드가 한 번에 하나의 스레드만 접근할 수 있도록 합니다.

public synchronized void method() {
    // 임계 영역
}

블록 동기화

특정 코드 블록을 동기화할 수 있습니다. 이 경우, synchronized 블록 내의 코드만 동기화됩니다.

public void method() {
    synchronized(this) {
        // 임계 영역
    }
}

정적 메서드 동기화

정적 메서드에 synchronized 키워드를 붙이면 클래스 레벨에서 동기화가 이루어집니다.

public static synchronized void staticMethod() {
    // 임계 영역
}

정적 블록 동기화

정적 블록을 동기화할 때는 클래스 객체를 사용합니다.

public static void staticMethod() {
    synchronized(MyClass.class) {
        // 임계 영역
    }
}

3. synchronized의 동작 원리

모니터 락(Monitor Lock)

synchronized 키워드는 객체의 모니터 락을 사용합니다. 스레드가 synchronized 메서드나 블록에 진입하면 해당 객체의 모니터 락을 획득하고, 메서드나 블록을 빠져나올 때 락을 해제합니다

예시

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

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

위 예시에서 increment와 getCount 메서드는 동기화되어 있어, 여러 스레드가 동시에 접근하더라도 count 변수의 일관성이 유지됩니다.

4. synchronized의 장단점

장점

  1. 데이터 일관성 유지: 여러 스레드가 동시에 접근할 때 데이터의 일관성을 유지할 수 있습니다.
  2. 간단한 사용법: synchronized 키워드를 사용하여 쉽게 동기화를 구현할 수 있습니다.

단점

  1. 성능 저하: 동기화로 인해 스레드가 대기 상태에 들어가면 성능이 저하될 수 있습니다.
  2. 교착 상태(Deadlock): 잘못된 동기화로 인해 교착 상태가 발생할 수 있습니다.
  3. 무한 대기(Blocked 상태): 스레드가 락을 획득하지 못할 경우 무한 대기 상태에 빠질 수 있습니다

5. 고급 예시

생산자-소비자 문제

생산자-소비자 문제는 멀티스레드 환경에서 자주 등장하는 문제로, synchronized를 사용하여 해결할 수 있습니다.

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 5;

    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (this) {
                while (queue.size() == MAX_SIZE) {
                    wait();
                }
                queue.add(value);
                System.out.println("Produced: " + value);
                value++;
                notify();
                Thread.sleep(1000);
            }
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (queue.isEmpty()) {
                    wait();
                }
                int value = queue.poll();
                System.out.println("Consumed: " + value);
                notify();
                   }

    public static void main(String[] args) {
        ProducerConsumer pc = new ProducerConsumer();
        Thread producerThread = new Thread(() -> {
            try {
                pc.produce();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                pc.consume();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}

위 예시에서 produce와 consume 메서드는 synchronized 블록을 사용하여 동기화됩니다. wait와 notify 메서드를 사용하여 생산자와 소비자 스레드 간의 협력을 구현합니다.

synchronized 키워드는 자바에서 멀티스레드 환경에서 동기화를 구현하는 데 매우 유용한 도구입니다. 이를 통해 데이터의 일관성을 유지하고, 경쟁 상태를 방지할 수 있습니다. 그러나 성능 저하와 교착 상태와 같은 단점도 있으므로, 상황에 맞게 적절히 사용해야 합니다.

반응형