자바의 ReentrantLock, ReentrantReadWriteLock, StampedLock 비교

한봉훈·2024년 10월 9일

자바에서는 다양한 동시성 제어 도구를 제공하고 있다. 그중에서도 ReentrantLock, ReentrantReadWriteLock, 그리고 StampedLock은 각기 다른 용도와 성능 최적화를 위해 설계되었다. 이 세 가지 락의 주요 특징과 차이점을 정리해 보았다.

1. ReentrantLock

ReentrantLock은 가장 기본적인 락이며, 재진입성을 지원한다. 재진입성이란, 동일한 스레드가 이미 획득한 락을 다시 획득할 수 있음을 의미한다. 이는 중첩된 함수 호출에서 데드락을 방지하는 데 중요한 역할을 한다.

주요 특징:

  • 재진입성: 동일한 스레드가 동일한 락을 여러 번 획득할 수 있으며, 획득한 만큼 해제해야 완전히 해제된다.
  • 공정성 선택: 공정 모드를 선택할 수 있어 락을 획득하는 순서가 보장될 수 있다.
  • 명시적인 락 제어: tryLock()과 같은 비차단 시도와 lockInterruptibly() 같은 인터럽트 가능한 락 대기 기능을 제공한다.
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();  // 락 획득
        try {
            // 크리티컬 섹션
        } finally {
            lock.unlock();  // 락 해제
        }
    }
}

장점:

  • 간단하고 직관적인 사용.
  • tryLock()을 통해 락을 기다리지 않고 다른 작업을 수행할 수 있다.

단점:

  • 읽기-쓰기 분리가 없어, 읽기와 쓰기 작업이 동일한 수준에서 처리된다.
  • 단순한 시나리오에서 오버헤드가 발생할 수 있다.

2. ReentrantReadWriteLock

ReentrantReadWriteLock은 읽기와 쓰기 작업을 구분하여 동시성을 높이는 락이다. 여러 스레드가 동시에 읽기 작업을 수행할 수 있지만, 쓰기 작업은 단일 스레드만 수행할 수 있다.

주요 특징:

  • 재진입성: 읽기 락과 쓰기 락 모두 재진입성을 지원한다.
  • 읽기와 쓰기 분리: 읽기 작업은 여러 스레드가 동시에 가능하며, 쓰기 작업은 단일 스레드만 가능하다.
  • 공정성 모드 선택: 락을 획득하는 순서를 제어할 수 있다.
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockExample {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

    public void readData() {
        readLock.lock();
        try {
            // 읽기 작업
        } finally {
            readLock.unlock();
        }
    }

    public void writeData() {
        writeLock.lock();
        try {
            // 쓰기 작업
        } finally {
            writeLock.unlock();
        }
    }
}

장점:

  • 읽기 작업이 많은 상황에서 동시성을 극대화할 수 있다.
  • 재진입성을 지원하여 동일한 스레드가 여러 번 락을 획득해도 문제가 없다.

단점:

  • 쓰기 작업이 있을 때 읽기 작업이 차단된다.
  • 낙관적 읽기(락 없이 읽기)를 지원하지 않는다.

3. StampedLock

StampedLock은 자바 8에서 도입된 락으로, 낙관적 읽기(Optimistic Read), 읽기 락, 쓰기 락을 모두 지원하는 락이다. 특히, 낙관적 읽기를 통해 락을 걸지 않고도 데이터를 읽을 수 있는 방식으로 성능 최적화를 꾀한다.

주요 특징:

  • 낙관적 읽기: 락 없이 데이터를 읽고, 중간에 쓰기 작업이 있었는지 확인할 수 있다.
  • 비 재진입성: StampedLock은 재진입성을 지원하지 않으며, 같은 스레드가 두 번 락을 요청하면 데드락이 발생할 수 있다.
  • 쓰기 락: 단일 스레드만 쓰기 작업을 수행할 수 있다.
import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    private double x, y;

    // 쓰기 잠금
    public void move(double deltaX, double deltaY) {
        long stamp = lock.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            lock.unlockWrite(stamp);
        }
    }

    // 낙관적 읽기
    public double distanceFromOrigin() {
        long stamp = lock.tryOptimisticRead();
        double currentX = x, currentY = y;

        if (!lock.validate(stamp)) {
            stamp = lock.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

장점:

  • 낙관적 읽기를 통해 읽기 작업이 많은 상황에서 성능이 뛰어나다.
  • 읽기 후 쓰기 작업 여부를 검증할 수 있어 락 없이 데이터를 안전하게 읽을 수 있다.

단점:

  • 재진입성을 지원하지 않아 같은 스레드가 두 번 락을 요청하면 데드락이 발생할 수 있다.
  • 코드가 복잡해질 수 있으며, 스탬프를 적절히 관리해야 한다.
특성ReentrantLockReentrantReadWriteLockStampedLock
재진입성지원 (락 중복 획득 가능)읽기 및 쓰기 락 모두 재진입성 지원지원하지 않음 (비재진입성)
읽기-쓰기 구분구분 없음 (읽기-쓰기 모두 동일)읽기와 쓰기 락 분리낙관적 읽기, 읽기 락, 쓰기 락
낙관적 읽기 지원지원하지 않음지원하지 않음지원 (락 없이 읽기 가능)
공정성선택 가능 (공정/비공정 모드)선택 가능 (공정/비공정 모드)지원하지 않음
성능기본 락읽기 작업이 많고 쓰기 작업이 적을 때 유리읽기 작업이 많을 때 성능 최적화 가능
복잡성비교적 간단중간가장 복잡 (스탬프 관리 필요)
profile
백엔드 기록

0개의 댓글