Computer Science/JAVA

Java의 lock

꽃요미 2024. 12. 8. 14:52

* Optimistic Lock

 - 변경 충돌이 적을 것이라 낙관적으로 가정하고 충돌 발생 시에만 재시도 하는 방식.

 

1. CAS

 - 읽기 락과 쓰기 락의 구분이 명확한 알고리즘으로, Java의 AtomicInteger, AtomicLong 같은 클래스에 적용됨.

 

 - 동작 원리

 1. 기존의 값과 새로운 값을 비교한 후, 동일할 때만 값을 교체합니다.

 2. 교체에 실패하면 재시도합니다.

 3. 하드웨어 명령어 수준에서 구현됨, CPU의 CAS 명령어 (CMPXCHG, LL/SC)를 사용함.

 4. Atomic 클래스 (AtomicInteger, AtomicLong, AtomicReference)가 이를 구현함.

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private final AtomicInteger counter = new AtomicInteger(0);

    public void increment() {
        int current;
        do {
            current = counter.get(); // 현재 값 읽기
        } while (!counter.compareAndSet(current, current + 1)); // CAS를 이용한 교체
    }

    public int get() {
        return counter.get();
    }
}

* 특징

- Lock-Free 알고리즘으로, 락을 걸지 않고 동시성을 제어함.

- 성능이 좋고, 스레드 경합이 적은 경우에 유리합니다.

- 단 ABA 문제가 발생할 수 있다.

 

2. Version Contorl ( 버전 관리 )

 - DB또는 객체의 버전 번호를 사용하여 동시성 문제를 감지하는 방법.

 

 - 동작 원리

 1. 엔티티에 버전 필드를 추가합니다.

 2. 트랜잭션이 데이터를 읽어올 때, 현재의 버전 정보도 함께 읽어옴.

 3. 데이터를 변경할 때, 변경 전후의 버전이 같은지 확인함.

 4. 다른 트랜잭션이 데이터를 수정했다면, 버전이 증가하기 때문에 충돌이 발생함.

 5. DB에서는 JPA, Hibernate의 Optimistic Lock이 버전 관리를 이용한 구현 방식.

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private Integer stockQuantity;

    @Version // 버전 관리에 사용
    private Integer version;

    // Getter, Setter
}

* 특징

- JPA, Hibernate에서 자주 사용되며, @Version 어노테이션으로 간단 구현 가능.

- 트랜잭션이 충돌다면 OptimisticLockException 예외가 발생함.

 

3. StampedLock

 - jdk 1.8에 추가된 StampedLock은 낙관적 읽기(Optimisitc Read) 기능을 제공. 스레드가 데이터 읽을 때 락 없이도 읽을 수 있음.

 

 - 동작원리

 1. StampedLock은 낙관적 읽기와 독점 락을 동시에 지원합니다.

 2. 읽기 작업은 락을 걸지 않고 낙관적 읽기로 데이터를 가져옴.

 3. 읽는 중에 데이터가 변경되었는지 validate(stamp) 메서드로 확인 가능.

 4. 변경이 발생하면, 읽기 락(Read Lock)을 획득하여 데이터를 다시 읽음.

 

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    private int sharedData = 0;

    // 읽기 작업 (낙관적 읽기)
    public int optimisticRead() {
        long stamp = lock.tryOptimisticRead();
        int data = sharedData;
        if (!lock.validate(stamp)) { // 데이터 변경 감지
            stamp = lock.readLock();
            try {
                data = sharedData;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return data;
    }

    // 쓰기 작업
    public void write(int value) {
        long stamp = lock.writeLock();
        try {
            sharedData = value;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

*특징

 - Optimistic Read를 지원하며, 락 없이 읽기 작업을 수행 가능함.

 - 데이터 변경을 감지할 수 있는 validate() 메서드가 존재함.

 - StampedLock은 ReentrantLock과 다르게 재진입이 불가능함.

 - 읽기 중에 데이터가 변경되었으면, 읽기 락을 다시 획득 함.

 

* 정리

구분 CAS Version Control StampedLock
구현 방식 CPU 수준의 CAS 명령어 버전 필드 추가 낙관적 읽기 / 쓰기 락
락 여부 X 락없이 진행 X 락 없이 버전 확인 X 필요 시 읽기 락 획득
충돌 처리 충돌 발생 시 재시도 충돌 발생시 OptimisticLockException 데이터 변경 감지 후 재시도
사용 환경 메모리 동기화에 유리 DB JPA에서 자주 사용 다중 읽기 환경에 최적화
성능 고성능 성능은 보통, 트랜잭션 충돌 시 재시도 고성능 읽기 최적화
예시 Atomic 클래스, ConcurrentHashMap JPA, Hibernate의 @Version StampedLock, 읽기 락 최적화

 

- Read/Write Lock?

 

* Pessimistic Lock (비관적 락)

 - 공유 자원에 대한 동시 접근 문제를 해결하기 위해 미리 락을 걸어 놓고 다른 트랜잭션이나 스레드가 해당 자원에 접근하지 못하도록 하는 방식의 락 전략.

 

1. ReentrantLock

 - 명시적으로 lock(), unlock()을 호출하며 공정성(Fairness) 설정, 중단 기능 락 등의 기능을 제공함.

 

2. Synchronized 키워드

 - 자바에서 제공하는 모니터 락(Monitor Lock) 메커니즘으로, 해당 블록이나 메서드에 동시에 하나의 스레드만 접근할 수 있도록 제한함.

 

3. @Lock(LockModeType.PESSIMISTIC_WRITE)

 - @Lock 어노테이션이 지원하는 LockModeType.PESSIMISTIC_WRITE 모드를 사용하는 것.

 - JPA에서 지원해준다.

 

 

- Shared Lock

- Exclusive Lock?