멀티스레드 환경에서 여러 스레드가 공유 자원에 접근할 때 락(lock)을 거는 방법은 크게 두 가지가 있습니다. 하나는 ReentrantLock(또는 ReadWriteLock)을 사용하는 것이고, 다른 하나는 synchronized 메서드 또는 블록을 사용하는 것입니다.
ReentrantLock 클래스는 Java 5부터 java.util.concurrent 패키지에서 제공되기 시작했습니다. 이 클래스는 Lock 인터페이스의 구현체로, 자바 공식 문서에 따르면 Lock 인터페이스는 synchronized 메서드나 블록으로 얻을 수 있는 기능보다 더 폭넓은 락 연산을 지원합니다.
ReentrantLock vs Synchronized 주요 차이점
| 번호 | 구분 | ReentrantLock | Synchronized |
|---|---|---|---|
| 1 | 락 획득 | lock() 메서드를 호출하여 스레드가 공유 자원에 대한 락을 획득합니다. | synchronized 키워드만 작성하면 락이 자동으로 획득됩니다. |
| 2 | 락 해제 | 개발자가 반드시 finally 블록에서 unlock() 메서드를 직접 호출해야 합니다. | 임계 영역을 벗어나면 락이 암묵적으로(자동으로) 해제됩니다. |
| 3 | 인터럽트 가능 여부 | lockInterruptibly() 메서드를 사용하면 락을 기다리는 스레드를 인터럽트할 수 있습니다. | 락을 기다리는 스레드를 인터럽트할 방법이 없습니다. |
| 4 | 공정성(Fairness) | 생성자에 fairness 파라미터가 있으며, true로 설정하면 가장 오래 기다린 스레드에게 우선적으로 락이 부여됩니다. | 특정한 접근 순서를 보장하지 않습니다. |
| 5 | 락 해제 순서 | 획득한 순서와 무관하게 어떤 순서로든 락을 해제할 수 있습니다. | 블록 구조에 따라 자동으로 해제되므로 개발자가 해제 순서를 임의로 조정할 수 없습니다. |
ReentrantLock 예제
public class ReentrantLockExample implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
try {
lock.lock();
// 공유 자원에 대한 작업 수행
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 반드시 finally 블록에서 락 해제
}
}
}Synchronized 예제
public class SynchronizedLockExample implements Runnable {
@Override
public void run() {
synchronized (resource) {
// 공유 자원에 대한 작업 수행
}
}
}어떤 것을 선택해야 할까?
synchronized는 사용법이 간단하고 락 해제를 직접 관리할 필요가 없어 대부분의 일반적인 상황에 적합합니다. 반면, 락 획득에 타임아웃을 설정하거나, 대기 중 인터럽트에 대응해야 하거나, 공정한(fair) 락이 필요한 등 세밀한 동시성 제어가 요구되는 경우에는 ReentrantLock이 더 나은 선택입니다.