재고 관리 로직을 통해 동시성 이슈에 대해서 정리해보자.
공유 자원과 임계 영역(Critical Section) 그리고 Race Condition
먼저, 스레드의 공유 자원에 대해 생각해보자.
스레드가 여럿일 경우 스레드는 힙 영역에 있는 모든 데이터를 공유한다. 힙에는 모든 객체와 객체의 속한 필드들이 저장되며 스레드에서 접근이 가능하다.
그렇다면 임계 영역(Critical Section)은 무엇일까 ?
이는, 공유 자원에 액세스하고 이에 대한 쓰기 작업을 수행하는 코드 세그먼트를 말한다.
예시는 아래와 같다. Item 클래스와 해당 클레스의 멤버 변수들은 힙에 할당된 공유 자원으로 모든 스레드에서 접근이 가능하고, 공유 자원의 값을 변경하는 코드 세그먼트를 보여준다.

마지막으로, Race Condition 이란?
동시에 여러 스레드가 병렬적으로 실행하여 생기는 일종의 동시성 버그이다.
예를 들어, 두 스레드가 count (ex>100)값을 증가시키려고 할 때 병렬 실행으로 인해 A 스레드가 증가한 값(101)을 업데이트하기 전에 B 스레드가 갱신되기 전 값(100)을 가져오면 하나의 count 가 손실된다.
위 내용을 코드로 확인해보자.
동시성 이슈
1. 동시성 이슈 테스트
위와 같은 동시성 이슈를 해결하기 위해 먼저 동시성 이슈 상황을 구현해보자.
간단하게 재고 로직을 구현하였고 서비스단에서 재고를 증가하는 메서드를 이용하여 테스트 할 것이다.
또한, 테스트 코드는 재고시스템으로 알아보는 동시성이슈 해결방법을 보고 참고하였다.
그래서 ExcutorService 나 CountDownLatch 의 동작에 대해서 간단히 정리해두었지만 추후 공부를 해야할 것 같다.
itemId가 1L이고 재고가 100인 item data 는 미리 저장해두었고 아래의 코드는 stock 을 100 번 증가하여 결과값이 200 이 되어야 맞다.
- 서비스 단 재고 증가 메서드
public class ItemServiceImpl implements ItemService{
..
@Override
@Transactional
public void incrementStock(Long itemId) {
/* 데이터 동기화 불가 */
Optional<Item> item = itemRepository.findById(itemId);
item.ifPresent(Item::increment);
}
}
- 테스트 코드
@Test
public void concurrencyIssueTest() throws InterruptedException {
int threadCount = 100;
/* 32 개의 스레드가 있는 스레드 풀 생성 */
ExecutorService executorService = Executors.newFixedThreadPool(32);
/* latch 가 대기해야하는 스레드 수 */
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
itemServiceImpl.incrementStock(1L);
} finally {
latch.countDown(); // count down by 1
}
});
}
latch.await(); // wait until latch counted down to 0
}
@AfterEach
public void printStock(){
Optional<Item> findItem = itemRepository.findById(1L);
findItem.ifPresent(
i -> System.out.println("남은 수량: "+i.getStock())
);
}
- 실행 결과

2. synchronized 로 해결하기
간단하게는 synchronized 를 사용하여 동시성 이슈를 예방할 수 있다.
synchronized 를 사용하면 해당 영역의 코드 블록이나 전체 메서드에서 한 번에 하나의 스레드만 실행할 수 있도록 한다.
이를 적용하여 재고 감소 메서드를 구현하고 테스트해보자. ( 예상 결과 : 100-100=0 )
- 서비스 단 재고 감소 메서드 ( ★주의 )
public class ItemServiceImpl implements ItemService{
..
@Override
public void decrementStock(Long itemId) {
/* 동시성 이슈를 고려한 synchronized 사용 */
synchronized (this){
Optional<Item> item = itemRepository.findById(itemId);
item.ifPresent(
i ->{
i.decrement();
itemRepository.saveAndFlush(i);
}
);
}
}
}
**주의할 점 : @Transactional 동작 방식(전처리 -> 로직 실행 -> commit) 중 로직을 실행하고 commit 을 하기 전 정합성이 깨지기 때문에 제거하고 실행
- 테스트 코드
@Test
@DisplayName("동시성 이슈 성공 테스트")
public void concurrencyIssue_success_Test() throws InterruptedException {
int threadCount = 100;
/* 32 개의 스레드가 있는 스레드 풀 생성 */
ExecutorService executorService = Executors.newFixedThreadPool(32);
/* latch 가 대기해야하는 스레드 수 */
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
itemServiceImpl.decrementStock(1L);
} finally {
latch.countDown(); // count down by 1
}
});
}
latch.await(); // wait until latch counted down to 0
}
- 실행 결과

3. synchronized 문제점
테스트는 성공했지만, 강의에서 언급된 서버 2대 이상일 때의 데이터 동기화는 어떻게 할 것인가에 대한 문제점이 있다.
정리
데이터 동기화에 대하여 정리를 해보았고 그에 대한 해결 방안으로 synchronized 사용 예제를 보았다.
다음에는 이 문제점을 해결할 수 있는 방안에 대해 정리해야겠다.
전체 코드 : https://github.com/dlask913/til/tree/concurrency_issue
GitHub - dlask913/til: Today I Learned
Today I Learned. Contribute to dlask913/til development by creating an account on GitHub.
github.com
참고
https://rollbar.com/blog/java-heap-space/
https://interviewsansar.com/what-is-critical-section-in-multithreading/
https://www.educative.io/answers/what-is-the-critical-section-problem-in-operating-systems
https://javarevisited.blogspot.com/2012/02/what-is-race-condition-in.html#axzz8CtV4sZka
https://www.baeldung.com/java-executor-service-tutorial
https://www.geeksforgeeks.org/synchronization-in-java/
'Web' 카테고리의 다른 글
| [JPA] 1:N 단방향 매핑과 N:1 양방향 매핑 (0) | 2023.10.01 |
|---|---|
| [Spring] CORS Configuration with Spring security (0) | 2023.09.17 |
| [JPA] Dirty Checking 동작 방식 및 성능 개선 (2) | 2023.09.03 |
| [Spring] Maven Scope 와 Gradle Configurations (0) | 2023.08.27 |
| [Spring] compiletime 과 runtime 에 대해서 (0) | 2023.08.13 |