스프링

ConcurrentHashMap

inle 2024. 10. 11. 09:22

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