Language/Java / / 2023. 8. 1. 16:47

[Java] 동시성 이슈 해결하기

반응형

동시에 여러 개의 요청이 들어오게 되면, 우리가 원하던 결과값이 나오지 않아 테스트 케이스 작성 시 오류가 발생하는 경우 많다.

 

이러한 오류를 레이즈 컨디션이라고 한다. 이러한 부분은 어떻게 해결해야 할까?

 

레이스 컨디션(Race Condition)

레이스 컨디션이란? 두 개 이상의 프로세스가 공통 자원을 병행적으로(ConCurrently) 읽거나 쓸 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 달라지는 상황을 말한다.

 

그렇다면 레이스 컨디션이 발생하게 된다면 어떻게 될까?

 

모든 프로세스 원하는 결과가 발생하는 것을 보장할 수 없기 때문에, 이러한 상황은 반드시 피해야만 한다. 중간에 값이 변경되는 경우에는 우리가 예측하기 불가능하고 실제 이런 코드들이 문제를 많이 발생시킨다.

 

아래에서 해결 방법을 알아보겠습니다.

 

1. synchronized 사용

synchronized는 해당 메서드에 한 개의 스레드만 접근이 가능하도록 해준다. 그러나 결론부터 말하자면 synchronized는 좋은 해결방법이 아니기 때문에 거의 사용하지 않는다.

 

일단 synchronized를 사용하려면 @Transactional이라는 애노테이션을 주석처리 해주어야 한다.

  • @Transactional은 StockService를 필드로 가지는 class를 새로 만들어 수행한다.
  • 새로 만들어진 class에서, transaction이 갱신되기 전에 다른 스레드가 값을 가져가서 레이스 컨디션 문제가 발생한다.

 

하지만 synchronized를 사용할 때 발생할 수 있는 문제가 있다.

  • synchronized는 각 프로세스 안에서만 보장이 된다.
  • 서버가 한 대 일 경우에는 문제가 없다.
  • 서버가 여러 개라면, 동시에 데이터에 접근이 가능하다.(실무에서는 여러 대의 서버를 보통 사용한다.)
  • 결국 여러 스레드에서 동시에 데이터에 접근이 가능하고 문제가 발생하게 된다.

 

2. Mysql을 활용

✔ Pessimistic Lock

 

실제로 데이터에 Lock을 걸어서 정합성을 맞추는 방법이다.

 

exclusive lock을 걸게 된다면, 다른 트랜잭션에서는 lock이 해제되기 전, 데이터를 가져갈 수 없게 된다.

 

데드락이 걸릴 수 있기 때문에 주의해서 사용하는 것이 좋다.

 

✔ Optimistic Lock

 

실제로 Lock을 이용하지 않고 버전을 이용함으로써 정합성을 맞추는 방법이다.

 

먼저 데이터를 읽은 후, update를 수행할 때 현재 내가 읽은 버전이 맞는지 확인하며 업데이트한다.

 

내가 읽은 버전에서 수정사항이 생겼을 경우 application에서 다시 읽은 후 작업을 수행해야 한다.

 

✔ Named Lock

 

이름을 가진 metadata locking이다.

 

이름을 가진 Lock을 획득한 후, 해제할 때까지 다른 세션은 이 lock을 획득할 수 없도록 한다.

 

주의해야 할 점

  • transaction이 종료될 때, lock이 자동으로 해제되지 않는다. 별도의 명령어로 해제를 수행해 주거나 선점시간이 끝나야 해제 가능하다.
  • pessimistic Lock과 비슷하지만 차이가 있다.
pessimistic Lock Named Lock
로우나 테이블 단위로 설정 메타데이터에 설정

 

⚠ Named Lock 기능

Named Lock은 MySQL 레벨에서 제공하는 기능입니다. 쿼리 문을 이용해 잠금을 획득하고 해제할 수 있습니다.

  • GET_LOCK(lock_name, timeout)
    • lock_name으로 Named Lock을 획득하려고 시도합니다. timeout 매개변수는 잠금을 획득할 수 없는 경우 오류를 반환하기 전 함수가 기다리는 시간을 지정합니다.
  • RELEASE_LOCK(lock_name)
    • lock_name으로 Named Lock을 해제합니다.
  • IS_FREE_LOCK(lock_name)
    • lock_name의 Named Lock이 사용 가능한지 확인합니다.
  • IS_USED_LOCK(lock_name)
    • lock_name의 Named Lock이 사용 중인지 확인합니다.

추가적으로 8.0 이전에는 Named Lock을 중첩해서 걸 수 없엇지만, 8.0 이후부터는 중첩해서 걸 수 있게 되면서 조금 더 복잡한 로직을 처리할 수 있게 됐습니다.

반응형

'Language > Java' 카테고리의 다른 글

[Java] CountDownLatch  (0) 2023.08.01
[Java] Executors / ExecutorService 사용법  (0) 2023.08.01
Enum(EnumMap, EnumSet)  (0) 2023.07.06
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유