menuhwang

[자바] 동시성 문제 본문

카테고리 없음

[자바] 동시성 문제

menuhwang 2023. 1. 6. 19:47

현재 글은 트랜잭션 락을 적용하지 않아 동시성 문제를 온전히 해결하지 못 하였습니다.

 

아래는 트랜잭션 락 부터 동시성 문제 해결까지 실습해보는 글의 첫 번째 글입니다.

 

동시성 문제 해결(1) - 트랜잭션 락

동시성 문제 해결을 위해 관련 내용을 공부하고 최종적으로 스프링과 Spring Data JPA 환경에서 적용한 과정을 정리하고자 한다. 동시성 문제란? 여러 스레드가 동시에 하나의 자원을 사용할 때 발

menuhwang.tistory.com

 

0. 개요

멋쟁이 사자처럼 백엔드 스쿨 2기 개인 프로젝트로 SNS를 구현 중이다.

최근에 포스트의 좋아요 기능을 추가하던 중 문득 동시에 좋아요를 누르면 어떻게 되지?라는 궁금증이 생겼고 전에 어렴풋이 들어봤던 동시성이 이런 것인가 싶어 찾아보게 되었다.

 

1. 동시성 문제란?

동시성 문제는 멀티 쓰레드 상황에서 발생한다.

 

게시물의 좋아요를 누르면 기존 좋아요 값을 가져와 +1을 한 뒤 저장하는 과정을 거친다.

 

두 명의 사용자가 각각 좋아요를 누르면 총 좋아요 수가 2가 되어야 한다.

 

위 그림처럼 여러 쓰레드가 순차적으로 동작한다면 문제가 없다.

 

하지만, 동시에 같은 값에 접근하여 수정을 한다면 문제가 발생한다.

 

두 사용자가 동시에 좋아요를 눌러 접근한 데이터가 모두 0이라면, +1을 한 1이 저장돼 정상 예상 값인 2와 다른 잘못된 값이 저장된다.

 

2. 동시성 문제 테스트

Post.java

public class Post {
    // ...
    public void likes() {
        likes++;
    }

    public void unlikes() {
        if (likes > 0) likes--;
    }
}

 

PostTest.java

@Test
@DisplayName("좋아요 : 동시성 테스트")
void like_multi_thread() throws InterruptedException {
    int numberOfThreads = 10;
    ExecutorService service = Executors.newFixedThreadPool(10);
    CountDownLatch latch = new CountDownLatch(numberOfThreads);
    Post post = POST.init();

    for (int i = 0; i < numberOfThreads; i++) {
        service.execute(() -> {
            post.likes();
            latch.countDown();
        });
    }
    latch.await();
    assertEquals(numberOfThreads, post.getLikes());
}
직접 Thread로 멀티 쓰레드를 연습해본 적이 있는데 ExcutorService라는 것은 처음 접해보았다.

 

위 테스트 코드를 돌려보면 기존 likes(), unlikes() 메소드 모두 정상적으로 통과한다.

 

로직 자체가 너무 간단해서 동시에 값을 접근하는 상황이 생기지 않기 때문으로 보인다.

 

쓰레드를 약 300개 정도로 늘려 테스트를 하면 간혹 통과를 하기도 하고 실패하기도 한다.

 

그래서 임시로 비즈니스 로직을 바꿔 테스트하는 것을 선택했다.

 

Post.java

public class Post {
    // ...
    public void likes() {
        try {
            int temp = likes;
            Thread.sleep(100);
            likes = temp + 1;
        }catch (InterruptedException e) {
        }
    }
}

 

likes 값을 가져온 후 100ms를 기다려 여러 쓰레드가 동시에 같은 값에 접근하도록 하였다.

 

그 결과 위 테스트 코드가 완벽히 실패하였다.

 

 

3. 동시성 문제 해결

Post.java

public class Post {
    // ...
    public synchronized void likes() {
        likes++;
    }

    public synchronized void unlikes() {
        if (likes > 0) likes--;
    }
}

 

동시성 문제 해결 방법은 여러 가지가 있었다.

 

그중 synchronized를 선택했다. 다른 방법은 아직 지식이 부족해서 이해하지 못하기 때문에...

 

synchronized를 적용한 메소드 동작 순서는 대충 이렇다.

 

어떤 한 쓰레드가 메소드를 호출한 상태에서 작업이 끝나기 전에 다른 쓰레드에서 이 메소드를 호출하면 앞서 호출한 쓰레드가 작업이 완료되기까지 기다리게 된다. (이것을 Lock이라고 한다.)

 

즉, 동시에 같은 값을 접근하여 수정하는 일이 발생하지 않는 것이다. 하지만, 다른 스레드의 작업을 기다려야 하기 때문에 그만큼 리소스 낭비가 발생한다는 문제가 있다.

 

4. 마무리

쓰레드, OS관련 지식이 없다 보니 이해하는데 한계가 있었다. 그래도 동시성 문제가 어떤 것인지 알게 되었다.

 

+ 동시성 문제를 해결하는 방법이 이것뿐일까?

 

검색하다 보니 트랜잭션 Isolation Level 설정으로도 해결을 해볼 수 있지 않을까라는 생각이 들었다.

 

OS, DB 공부가 필요해 보인다.

 

자바에서 동시성 문제를 해결하는 3가지 키워드

 

자바에서 동시성 문제를 해결하는 3가지 키워드

개요

devwithpug.github.io

 

가시성과 원자성

 

[Java] Concurrent Programming - 가시성과 원자성

멀티 스레드 프로그래밍 소개 멀티 스레드를 다루는 과정의 기초가 되는 가시성과 원자성을 정의해볼 것 사실 가시성과 원자성이라고 하는 단어는 문제를 해결하기 위한 원칙이다. 바꿔 말해 Mu

velog.io

 

@Transactional 정리 및 예제

 

[Spring] @Transactional 정리 및 예제

[Spring] @Transactional 정리 및 예제 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ 스프링 어노테이션 @Transactional ] 입니다. : ) 들어가기 앞서...... SI를 할때, 현재 어느 쇼핑몰 운영을 맡으며 개발 소

javawork.tistory.com

 

Comments