본문 바로가기

Programming/Java

JAVA synchronized 키워드와 ReentrantLock의 차이점 (멀티스레딩)

반응형

Java에서 멀티스레딩을 구현할 때 동기화(synchronization)는 필수적인 요소다. 대표적으로 사용하는 두 가지 방법이 있는데, 바로 synchronized 키워드와 ReentrantLock 클래스다. 이 두 방식은 동시성 제어를 위한 도구라는 공통점이 있지만, 사용 방법과 특징에서 많은 차이를 가진다. 이 문서에서는 두 방식의 차이점을 명확하게 비교하고 실전에서의 활용 방식을 알아보자.

* 동기화(Synchronization)란?

멀티스레드 환경에서는 여러 스레드가 동시에 공유 자원에 접근할 수 있기 때문에, 데이터 일관성과 안정성을 확보하기 위해 동기화가 필요하다. 동기화를 통해 한 번에 하나의 스레드만 특정 코드 블록을 실행할 수 있도록 제어할 수 있다.


1. synchronized 키워드

synchronized는 Java에서 가장 기본적인 임계 영역(critical section) 보호 수단이다.

사용 방식

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

또는 블록 단위로도 사용 가능:

synchronized (this) {
    // 동기화할 코드
}

특징

  • 사용이 간단하고 가독성이 좋음
  • 자동으로 락을 획득하고 해제함 (try-finally 불필요)
  • this, 클래스 객체, 또는 사용자 정의 객체를 모니터로 사용
  • 재진입 가능 (Reentrant): 같은 스레드가 여러 번 락 획득 가능

2. ReentrantLock 클래스

ReentrantLockjava.util.concurrent.locks 패키지에 포함된 명시적 락 클래스다.

사용 방식

private final ReentrantLock lock = new ReentrantLock();

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}

특징

  • 명시적으로 락을 획득(lock)하고 해제(unlock)해야 함
  • try-finally 블록 필수
  • tryLock(), lockInterruptibly() 등 다양한 제어 기능 제공
  • 공정성(Fairness) 설정 가능 (new ReentrantLock(true))
  • 조건 변수(Condition)를 사용한 세밀한 쓰레드 제어 가능

3. 비교 정리

항목 synchronized ReentrantLock
기본 위치 키워드 (JVM 수준) 클래스 (java.util.concurrent)
락 해제 방식 자동 (블록 벗어나면 해제) 수동 (unlock() 호출 필요)
조건 변수 지원 없음 Condition 인터페이스로 지원
공정성(Fairness) 제어 불가능 가능 (new ReentrantLock(true))
인터럽트 응답 불가능 (wait() 중 인터럽트 무시) 가능 (lockInterruptibly() 제공)
성능 간단한 경우 유리 복잡한 락 제어 시 유리

4. 언제 어떤 걸 써야 할까?

상황 권장 방법
동기화 로직이 단순하고 구조가 명확한 경우 synchronized
락 획득 시도 타임아웃, 인터럽트 응답이 필요한 경우 ReentrantLock
조건 변수로 세밀한 컨트롤이 필요한 경우 ReentrantLock
실수를 줄이고 가독성을 우선시하고 싶은 경우 synchronized

실전 예제: 은행 계좌 동시 접근

synchronized 예제

public class Account {
    private int balance = 1000;

    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
}
  • 단순한 출금 로직에는 synchronized만으로 충분함

ReentrantLock 예제 (타임아웃과 조건 변수 사용)

public class Account {
    private final ReentrantLock lock = new ReentrantLock();
    private int balance = 1000;

    public boolean withdraw(int amount) throws InterruptedException {
        if (lock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (balance >= amount) {
                    balance -= amount;
                    return true;
                }
                return false;
            } finally {
                lock.unlock();
            }
        } else {
            // 락을 얻지 못했을 때의 처리
            return false;
        }
    }
}
  • tryLock()을 사용해 일정 시간 내 락을 획득하지 못하면 실패 처리 가능

synchronized는 간결하고 안정적인 기본 동기화 수단이고, ReentrantLock더 많은 제어와 유연성을 제공하는 고급 동기화 도구다. 상황에 따라 두 방법을 적절히 선택하는 것이 성능과 안정성 모두에 이점을 줄 수 있다.

실무에서는 두 방식을 혼용하지 않도록 주의하며, 각 방식의 특성을 충분히 이해한 후 선택적으로 사용하는 것이 바람직하다.

반응형