1. ConcurrentMap과 ConcurrentHashMap
- **ConcurrentMap**은 자바의 인터페이스로, 여러 스레드가 동시에 접근하는 상황에서 안전하게 데이터를 저장하거나 읽을 수 있도록 설계된 맵입니다.
- **ConcurrentHashMap**은 ConcurrentMap의 구현체로, HashMap과 비슷한 방식으로 데이터를 저장하지만, 스레드 안전성을 제공하기 때문에 동시성 제어가 가능합니다.
- 즉, 여러 스레드가 동시에 리뷰 데이터를 추가하거나 읽으려고 할 때, 충돌 없이 안전하게 처리할 수 있습니다.
- 일반적인 HashMap은 스레드 간의 동시 작업에서 문제를 일으킬 수 있지만, ConcurrentHashMap은 내부적으로 락을 효율적으로 관리하여 스레드 안전성을 보장합니다.
왜 ConcurrentHashMap을 사용하는가?
- 이 서비스에서는 여러 사용자가 동시에 리뷰를 작성하거나 조회할 수 있습니다. 이러한 경우, 동시성 문제가 발생하지 않도록 스레드 안전한 자료구조인 ConcurrentHashMap을 사용하는 것이 중요합니다.
- 여러 스레드가 동시에 ConcurrentHashMap에 접근하여 데이터를 읽기 또는 쓰기를 할 때, ConcurrentHashMap은 부분적인 락(Segment-based Locking)을 사용하여 성능을 최적화하면서도 데이터 일관성을 유지합니다.
private final ConcurrentMap<Long, List<Review>> reviewCache = new ConcurrentHashMap<>();
ConcurrentMap<Long, List<Review>> reviewCache: 상품 ID (Long)를 키로, 해당 상품에 대한 리뷰 리스트 (List<Review>)를 값으로 저장하는 맵입니다.
- ConcurrentMap은 스레드 안전하기 때문에 여러 스레드가 동시에 리뷰를 추가하거나 조회할 수 있습니다.
- 리뷰 캐시 역할을 합니다. 특정 상품에 대한 리뷰가 캐시에 있으면, 데이터베이스에 접근하지 않고 바로 캐시에서 가져올 수 있습니다.
Review review = new Review();
review.setProduct(product);
review.setUserId(requestDto.getUserId());
review.setScore(requestDto.getScore());
review.setContent(requestDto.getContent());
review.setImageUrl(imageUrl);
review.setCreatedAt(LocalDateTime.now());
product.addReview(requestDto.getScore());
reviewRepository.save(review);
reviewCache.compute(productId, (key, reviews) -> {
if (reviews == null) {
reviews = new ArrayList<>();
}
reviews.add(review);
return reviews;
});
compute() 메서드: ConcurrentHashMap에서 제공하는 메서드로, 특정 키에 대한 값을 계산하거나 수정할 때 사용하는 메서드입니다.
- 파라미터 1: productId는 키로 사용됩니다. 즉, 특정 상품에 대한 리뷰 리스트를 관리합니다.
- 파라미터 2: (key, reviews)는 람다 표현식입니다. 현재 productId에 해당하는 리뷰 리스트를 가져와서 새로운 리뷰를 추가하거나, 해당 키에 대한 값이 없다면 새로운 리스트를 생성합니다.
작동 방식:
- compute()는 productId에 해당하는 리뷰 리스트를 가져오고 만약 없으면 새로운 ArrayList<Review>()를 생성합니다.
- 이후 해당 리스트에 새로운 리뷰를 추가한 뒤, 다시 ConcurrentMap에 저장합니다.
- 이 메서드는 스레드 안전하게 동작하므로, 여러 스레드가 동시에 같은 productId에 대한 리뷰를 추가해도 안전합니다.
List<Review> reviews = reviewCache.computeIfAbsent(productId, id -> reviewRepository.findByProductIdOrderByCreatedAtDesc(id));
- computeIfAbsent(): ConcurrentHashMap에서 제공하는 메서드로, 특정 키(productId)에 해당하는 값이 없으면, 제공한 함수를 실행하여 값을 계산하고, 맵에 저장한 후 반환합니다.
- 캐시 사용: 만약 productId에 해당하는 리뷰 리스트가 캐시에 있다면, 그것을 반환하고, 없으면 데이터베이스에서 조회하여 캐시에 저장한 후 반환합니다.
- 이 방식으로 캐시를 활용해 데이터베이스 접근을 줄이고, 조회 성능을 높일 수 있습니다.
3. ConcurrentHashMap의 장점 요약
- 스레드 안전: 여러 스레드가 동시에 리뷰를 추가하거나 조회해도, ConcurrentHashMap은 내부적으로 락을 관리하여 데이터의 일관성을 보장합니다.
- 성능 최적화: 부분적으로만 락을 사용하므로, 성능 저하가 적습니다. 또한, 캐시를 활용하여 반복적인 데이터베이스 접근을 줄일 수 있습니다.
- compute()와 computeIfAbsent(): 이 메서드들을 통해 안전하게 값을 업데이트하거나, 값이 없을 때만 계산하여 추가하는 로직을 쉽게 구현할 수 있습니다.
'스프링' 카테고리의 다른 글
ConcurrentHashMap vs 레코드 락 (0) | 2024.10.11 |
---|---|
MVP 단계 트러블 슈팅 (0) | 2024.04.12 |
Mybatis와 JPA (2) | 2024.03.12 |
스프링컨테이너와 빈 (0) | 2024.03.03 |
IoC & DI (0) | 2024.02.25 |