여러 스레드가 동시에 같은 인스턴스의 값에 접근하면서 발생하는 문제
스레드는 cpu 작업의 단위 인데, 멀티스레드 방식은 멀티태스킹 방식 중 하나의 코어에서 여러 스레드를 사용하여 작업을 처리하는 방식이다.
여러 스레드의 자원을 사용하기 때문에 성능상 강력하지만, 자원을 공유하기 때문에 이런 동시성 이슈도 발생하게 된다.
재고를 감소시키는 클래스를 실행할 때, 동시에 100군데에서 요청이 왔다고 가정해 보자.
재고를 1감소 시키는 로직이 실행되면 3초후에 재고가 1이 감소되는데 3초가 지나기전에 다른 스레드에서 공유데이터에 접근하면 문제가 생긴다.
레이스 컨디션(Race Condition)
둘 이상의 스레드가 공유 데이터에 액세스할 수 있고 동시에 변경하려고 할 때 발생하는 문제
둘 이상의 스레드 : 요청
공유 데이터 : 재고 데이터
동시에 변경하려고 할 때 : 업데이트 할때
발생하는 문제 : 값이 정상적으로 바뀌지 않는 문제
자바의 synchronized 어노테이션을 활용하는 방법이다.
@Transactional
public synchronized void decrease(long id, long quantity) {
}
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimisticLock(long id);
database Lock 을 이용하는 방식
@Entity
public class Stock {
@Version
private long version;
}
@Lock(value = LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithOptimisticLock(long id);
@Service
public class OptimisticLockStockFacade {
public void decrease(long id, long quantity) throws InterruptedException {
while (true) {
try {
optimisticLockStockService.decrease(id, quantity);
break;
} catch (Exception e){
Thread.sleep(50);
}
}
}
}
Lock 을 이용하지 않고 버전관리를 이용하는 방법
@Component
public class NamedLockStockFacade {
public void decrease(long id, long quantity) {
try {
lockRepository.getLock(String.valueOf(id));
stockService.decrease(id, quantity);
} finally {
lockRepository.releaseLock(String.valueOf(id));
}
}
}
// 실무에선 기존 entity 말고 별도의 jdbc등을 사용해야 한다
@Query(value = "select get_lock(:key, 1000)", nativeQuery = true)
void getLock(String key);
@Query(value = "select release_lock(:key)", nativeQuery = true)
void releaseLock(String key);
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void decrease(long id, long quantity) {
Stock stock = stockRepository.findById(id).orElseThrow();
stock.decrease(quantity);
}
이름을 가진 메타데이터 Lock을 이용하는 방법 (분산Lock을 구현할 때 사용)