반응형
Java에서 멀티스레딩을 구현할 때 동기화(synchronization)는 필수적인 요소다. 대표적으로 사용하는 두 가지 방법이 있는데, 바로 synchronized 키워드와 ReentrantLock 클래스다. 이 두 방식은 동시성 제어를 위한 도구라는 공통점이 있지만, 사용 방법과 특징에서 많은 차이를 가진다. 이 문서에서는 두 방식의 차이점을 명확하게 비교하고 실전에서의 활용 방식을 알아보자.
* 동기화(Synchronization)란?
멀티스레드 환경에서는 여러 스레드가 동시에 공유 자원에 접근할 수 있기 때문에, 데이터 일관성과 안정성을 확보하기 위해 동기화가 필요하다. 동기화를 통해 한 번에 하나의 스레드만 특정 코드 블록을 실행할 수 있도록 제어할 수 있다.
1. synchronized 키워드
synchronized는 Java에서 가장 기본적인 임계 영역(critical section) 보호 수단이다.
사용 방식
public synchronized void increment() {
count++;
}
또는 블록 단위로도 사용 가능:
synchronized (this) {
// 동기화할 코드
}
특징
- 사용이 간단하고 가독성이 좋음
- 자동으로 락을 획득하고 해제함 (try-finally 불필요)
- this, 클래스 객체, 또는 사용자 정의 객체를 모니터로 사용
- 재진입 가능 (Reentrant): 같은 스레드가 여러 번 락 획득 가능
2. ReentrantLock 클래스
ReentrantLock은 java.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은 더 많은 제어와 유연성을 제공하는 고급 동기화 도구다. 상황에 따라 두 방법을 적절히 선택하는 것이 성능과 안정성 모두에 이점을 줄 수 있다.
실무에서는 두 방식을 혼용하지 않도록 주의하며, 각 방식의 특성을 충분히 이해한 후 선택적으로 사용하는 것이 바람직하다.
반응형
'Programming > Java' 카테고리의 다른 글
쓰레드 풀(Thread Pool)의 개념과 최적화 방법 (1) | 2025.04.15 |
---|---|
JAVA 데드락(Deadlock)과 이를 방지하는 방법 (교착상태) (0) | 2025.04.15 |
JAVA final, finally, finalize의 차이점 정리 (0) | 2025.04.15 |
JAVA 불변 객체(Immutable Object)란 무엇이며, 어떻게 만들 수 있을까 (0) | 2025.04.15 |
JAVA equals()와 hashCode() 오버라이딩할 때 주의할 점 (1) | 2025.04.10 |