[java] 스레드 동기화 방법

Java에서 여러 스레드가 동시에 실행될 때, 동일한 데이터나 자원에 접근하는 경우 데이터의 일관성과 안전성을 보장하기 위해 스레드 동기화가 필요합니다. Java는 스레드 동기화를 지원하기 위한 여러 가지 메커니즘을 제공합니다. 이번 포스트에서는 가장 일반적인 스레드 동기화 방법 중 락(lock)을 이용한 방법을 알아보겠습니다.

1. synchronized 키워드

Java에서 가장 기본적으로 제공되는 스레드 동기화 방법은 synchronized 키워드를 이용하는 것입니다. synchronized 키워드를 메서드나 블록에 적용함으로써 해당 영역을 잠금(lock)으로 지정할 수 있습니다. 이 방법은 단순하고 간편하지만, 성능상의 이슈가 발생할 수 있습니다.

public synchronized void synchronizedMethod() {
    // 동기화 필요한 작업 수행
}
public void someMethod() {
    synchronized (this) {
        // 동기화 필요한 작업 수행
    }
}

2. ReentrantLock 클래스

ReentrantLock 클래스는 Lock 인터페이스를 구현한 클래스로, synchronized 키워드보다 더 세밀한 제어가 가능합니다. ReentrantLock 클래스는 락 획득과 해제를 명시적으로 지정할 수 있으며, 공정성(fairness) 설정과 타임아웃(timeout) 설정 등 다양한 기능을 제공합니다.

Lock lock = new ReentrantLock();

public void someMethod() {
    lock.lock();
    try {
        // 동기화 필요한 작업 수행
    } finally {
        lock.unlock();
    }
}

3. AtomicInteger 클래스

AtomicInteger 클래스는 원자적(atomic)인 연산을 제공하여 스레드 간의 경쟁 상태(race condition)를 방지합니다. AtomicInteger는 정수형 변수의 원자적인 증가, 감소, 갱신 등의 연산을 제공하므로, 명시적인 동기화 없이 다중 스레드에서 안전하게 사용할 수 있습니다.

AtomicInteger counter = new AtomicInteger();

public void incrementCounter() {
    counter.getAndIncrement();
}

4. volatile 키워드

volatile 키워드는 변수를 메인 메모리에 저장하고, 스레드 간의 변경 내용을 즉시 반영하도록 보장합니다. volatile 키워드는 스레드 간의 가시성(visibility)을 보장하지만, 원자적인 연산을 제공하지는 않습니다. 따라서 volatile 키워드는 단순히 변수의 변경 내용을 다른 스레드에게 알리는 용도로 사용됩니다.

private volatile boolean stopFlag = false;

public void stop() {
    stopFlag = true;
}

public void someMethod() {
    while (!stopFlag) {
        // 작업 수행
    }
}

결론

Java에서 스레드 동기화는 여러 가지 방법을 통해 구현할 수 있습니다. synchronized 키워드를 이용한 방법은 간편하지만 성능 이슈가 있을 수 있으며, ReentrantLock 클래스는 더 세밀한 제어가 가능합니다. AtomicInteger 클래스는 원자적인 연산을 제공하여 스레드 간의 경쟁 상태를 방지하고, volatile 키워드는 가시성을 보장합니다. 적절한 동기화 방법을 선택하여 스레드로부터 데이터의 일관성과 안전성을 보장하는 것이 중요합니다.

참고 자료