[Spring] 동시성 문제(3) - 비관적 락

Kyungmin·2024년 6월 19일
0

Spring

목록 보기
24/39

저번 포스트 동시성문제(2) 에서는 자바의 synchronized 를 사용하여 동시성 문제를 해결할 수 있는지, 어떻게 적용하는지 알아보았다. 이번에는 락을 통해 synchronized 로는 해결하지 못하는 문제를 어떻게 해결할 수 있는지 알아보겠다.

1. 락(Lock)

  • 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 하나의 커넥션만 변경할 수 있게 해주는 기능
  • 동시성을 제어하기 위한 기능

락(Lock) 의 종류

  1. 공유 락 (shared lock, Read Lock, S-Lock)
    공유락은 데이터를 변경하지 않는 읽기 작업을 위해 잠그는 것을 말한다.

  2. 배타 락(Exclusive Lock, Write Lock, X-Lock)
    배타락은 데이터를 변경하는 작업을 위해 잠그는 것을 말한다.

이번 포스트에서는 락의 종류중 배타락에서의 '비관적 락(Pessimistic Lock)' 으로 어떻게 동시성 문제를 해결할 수 있는지 알아보겠다.

2. 비관적 락(Pessimistic Lock)

  • 실제로 데이터에 락을 걸어서 정합성을 맞추는 방법
  • 배타락을 걸게되면 다른 트랜잭션에서는 락이 해제되기 전에 락을 걸고 데이터를 가져갈 수 없게 된다.

언제 사용?

  • 데이터 충돌이 빈번하게 발생할 가능성이 높은 경우
  • 동시에 여러 트랜잭션이 같은 데이터를 수정하려는 경우

코드로 비관적 락을 어떻게 적용할 수 있는지 알아보자.
.
.
StockRepository

  • @Lock(LockModeType.PESSIMISTIC_WRITE) 을 통해 비관적 락을 설정할 수 있다.
@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("테스트 통과");
    }


이와 같이 정상적으로 테스트코드가 성공하는 것을 알 수 있다.

3. 비관적 락의 동작

1. 스레드1 이 락을 걸고 데이터를 가져간다.
2. 그 후 스레드2 가 락을 걸로 데이터 획득을 시도한다.
3. 스레드1 이 이미 자원점유중이므로 잠시 대기한다.
4. 스레드1 의 작업이 모두 완료가 된다면 스레드2가 락을 걸로 데이터를 가져갈 수 있게 된다.

4. 비관적 락의 장단점

장점

  1. 충돌이 빈번하게 일어난다면, 낙관적 락보다 성능이 좋을 수 있다.
  2. 락을 통해 업데이트를 제어하기때문에 데이터 정합성 보장

단점

  1. 별도의 락을 잡기 때문에 성능 감소의 가능성

.
.

다음 포스트에서는 낙관적 락에 대해서 알아보겠다.

profile
Backend Developer

0개의 댓글

관련 채용 정보