저번 포스트 동시성문제(2) 에서는 자바의 synchronized 를 사용하여 동시성 문제를 해결할 수 있는지, 어떻게 적용하는지 알아보았다. 이번에는 락을 통해 synchronized 로는 해결하지 못하는 문제를 어떻게 해결할 수 있는지 알아보겠다.
- 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 하나의 커넥션만 변경할 수 있게 해주는 기능
- 동시성을 제어하기 위한 기능
공유 락 (shared lock, Read Lock, S-Lock)
공유락은 데이터를 변경하지 않는 읽기 작업을 위해 잠그는 것을 말한다.
배타 락(Exclusive Lock, Write Lock, X-Lock)
배타락은 데이터를 변경하는 작업을 위해 잠그는 것을 말한다.
이번 포스트에서는 락의 종류중 배타락에서의 '비관적 락(Pessimistic Lock)' 으로 어떻게 동시성 문제를 해결할 수 있는지 알아보겠다.
- 실제로 데이터에 락을 걸어서 정합성을 맞추는 방법
- 배타락을 걸게되면 다른 트랜잭션에서는 락이 해제되기 전에 락을 걸고 데이터를 가져갈 수 없게 된다.
코드로 비관적 락을 어떻게 적용할 수 있는지 알아보자.
.
.
StockRepository
@Repository
public interface StockRepository extends JpaRepository<Stock, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimisticLock(Long id);
}
PessimisticLockStockService
@Transactional
public void decrease(Long id, Long quantity) {
Stock stock = stockRepository.findByIdWithPessimisticLock(id);
stock.decrease(quantity);
stockRepository.save(stock);
}
StockServiceTest
@Test
@DisplayName("비관적 락을 걸었을 경우")
public void sameRequest() throws InterruptedException {
int threadCount = 100;
// ExecutorService : 비동기로 실행하는 작업을 단순화하여 사용할 수 있도록 도와주는 자바의 API
ExecutorService executorService = Executors.newFixedThreadPool(32);
// CountDownLatch : 다른 스레드에서 수행중인 작업이 완료될 때 까지 대기할 수 있도록 도와주는 클래스
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i=0; i<100; i++) {
executorService.submit( () -> {
try {
// 서비스 로직 변경 !
pessimisticLockStockService.decrease(1L, 1L);
} finally {
latch.countDown();
}
});
}
latch.await();
Stock stock = stockRepository.findById(1L).orElseThrow();
assertEquals(0, stock.getQuantity(),"테스트 미통과");
System.out.println("테스트 통과");
}
이와 같이 정상적으로 테스트코드가 성공하는 것을 알 수 있다.
1. 스레드1 이 락을 걸고 데이터를 가져간다.
2. 그 후 스레드2 가 락을 걸로 데이터 획득을 시도한다.
3. 스레드1 이 이미 자원점유중이므로 잠시 대기한다.
4. 스레드1 의 작업이 모두 완료가 된다면 스레드2가 락을 걸로 데이터를 가져갈 수 있게 된다.
.
.
다음 포스트에서는 낙관적 락에 대해서 알아보겠다.