Synchronized : 해당 메서드에 하나의 쓰레드만 접근이 가능하게 됨.
⇒ 서버가 한대일때 동시성 제어 가능.

⇒ 탈락 : 충돌이 많이 발생하지 않는 환경에 적합.

@Version
private Long version;
@Lock(value = LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithOptimisticLock(Long id);
업데이트 실패했을때 재시도 해야하므로 while 문으로 감싸줌
수량 감소에 실패한다면 50초 뒤에 재시도.
@Service
public class OptimisticLockStockFacade {
private OptimisticLockStockService optimisticLockStockService;
public OptimisticLockStockFacade(OptimisticLockStockService optimisticLockStockService) {
this.optimisticLockStockService = optimisticLockStockService;
}
public void decrease(Long id, Long quantity) throws InterruptedException {
while (true) {
try {
optimisticLockStockService.decrease(id, quantity);
break;
} catch (Exception e) {
Thread.sleep(50);
}
}
}
}
NamedLock : 이름을 가진 메타데이터 Lock , 락을 획득하고 해제 할때까지 다른 세션을 이를 획득할 수 없음.
트렌잭션이 종료될때 락이 자동으로 해제되지 않기때문에 별도의 명령어로 해제를 실행해주거나 선점시간이 끝나야 락이 해제됨.

세션 1이 1이라는 이름으로 락을 획득
세션 2는 세션1이 락을 해재한후 락을 획득할 수 있음.
@Query(value = "select get_lock(:key, 3000)", nativeQuery = true)
void getLock(String key);
@Query(value = "select release_lock(:key)", nativeQuery = true)
void releaseLock(String key);
public void purchaseItem(Long itemId, PurchaseRequestDto requestDto) {
try {
//NameLock 획득
lockItemRepository.getLock(itemId.toString());
User user = userService.findUser(requestDto.getEmail());
Item item = itemService.findItem(itemId);
checkStock(item);
Purchase purchase = Purchase.builder().item(item).user(user).build();
purchaseRepository.save(purchase);
item.sellOne();
} finally {
//NamedLock 해제
lockItemRepository.releaseLock(itemId.toString());
}
}
트렌젝션 종료시에 락해제 세션관리를 잘 해줘야 하기때문에 주의해서 사용해야하고 실제로 사용할때는 구현방법이 복잡 할 수 있음.
실제로 우리 코드에서 테스트 해봤을때 동시성 제어가 잘 되지 않았음.ㅜㅜ
Lettuce : spinLock 을 사용하므로 재시도 로직 짜주어야함.
setnx 사용.
namedLock과 거의 비슷. 다른점: 레디스 사용+세션관리에 신경안써도 됨
*spinLock : 락을 획득하려는 쓰레드가 락을 획득할 수 있는지 반복적으로 확인하는 방법

일정시간 이후에 락 획득을 재시도 하는 로직 필요.
reddion
채널을 하나 만들고 락을 점유중인 쓰레드가 락 획득하려는 쓰레드에게 해제를 알려주면 안내를 받은 쓰레드가 락획득을 시도함.
별도의 재시도 로직 필요하지 않음.
@Transactional
public boolean purchaseItem(Long itemId, PurchaseRequestDto requestDto) {
User user = userService.findUser(requestDto.getEmail());
Item item = itemService.findItem(itemId);
String lockKey = "item_lock_" + itemId;
RLock rLock = redissonClient.getLock(lockKey);
try {
boolean available = rLock.tryLock(5, 3, TimeUnit.SECONDS);
if (!available) {
return false;
}
checkStock(item);
Purchase purchase = Purchase.builder().item(item).user(user).build();
purchaseRepository.save(purchase);
item.sellOne();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock(); // (4)
}
return false;
}