ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Core - 쓰레드 로컬(동시성문제2)
    FrameWork/Spring&Spring-boot 2024. 3. 5. 13:58

    동시성 문제 - 예제 코드

    동시성 문제가 어떻게 발생하는지 단순화해서 알아보자

     

    테스트에서도 lombok을 사용하기 위해 다음 코드를 추가하자

    build.gradle

    dependencies{
        ...
        //테스트에서 lombok 사용
        testCompileOnly 'org.projectlombok:lombok'
    	testAnnotationProcessor'org.projectlombok:lombok:'
    }

     

    이렇게 해야 테스트 코드에서 @Slfj4 같은 어노테이션이 작동한다.

     

    FieldService

    @Slf4j
    public class FieldService {
    
        private String nameStore;
    
        public String logic(String name){
            // 1. 파라미터 name값, nameStore 값 출력
            log.info("저장 name={} -> nameStore={}",name, nameStore);
    
            // 2.파라미터로 들어온 name nameStore 필드에 저장
            nameStore = name;
            sleep(1000);
    
            // 3. nameStore값 조회
            log.info("조회 nameStore={}", nameStore);
            return nameStore;
        }
    
        private void sleep(int millis){
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    
    }

     

     

    FieldServiceTest

    @Slf4j
    public class FieldServiceTest {
    
        private FieldService fieldService = new FieldService();
    
        @Test
        void field(){
            log.info("main start");
    
    
    //        Runnable userEx = new Runnable() {
    //            @Override
    //            public void run() {
    //                fieldService.logic("userA");
    //            }
    //        };
    
            // 보통 스레드는 2개가 경합을 할때 문제가 생긴다.
            // 그래서 멀티 스레드 환경을 구현한다.
            Runnable userA = () -> {
                fieldService.logic("userA");
            };
    
            Runnable userB = () -> {
                fieldService.logic("userB");
            };
    
            Thread threadA = new Thread(userA);
            threadA.setName("thread-A");
            Thread threadB = new Thread(userB);
            threadB.setName("thread-B");
    
            threadA.start();
            sleep(2000); //동시성 문제 발생X
            threadB.start();
    
            //메인쓰레드가 종료되어서 threadB가 정상적으로 실행되지 않고 종료된다.
            //메인 쓰레드 종료 대기
            sleep(3000);
            log.info("main exit");
        }
    
        private void sleep(int mills){
            try {
                Thread.sleep(mills);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

     

     

    순서대로 실행

    sleep(2000)을 설정해서 thread-A의 실행이 끝나고 나서 thread-B가 실행하도록 해보자

    참고로 FieldService.logic()메서드는 내부에 sleep(1000)으로 1초의 지연시간이 있다. 따라서 1초 이후에호출하면 순서대로 실행이 가능하다. 여기서는 넉넉하게 threadA의 시행을 대기하는 시간을 2초(2000ms)로 설정했다.

    ....
    sleep(2000); //동시성 문제 발생x
    // sleep(100); 동시성 문제 발생O
    ...

     

     

    실행결과

     

    1.

    threadA.start()메서드를 실행하면 Runable 인터페이스형의 인스턴스 userA에 정의된 run메서드의 실행내용이 실행된다.그럼 1)fieldService.logic()이 실행되면서 logic의 파라미터로 "userA"가 넘어간다.  2)처음에 넘어옴 nameStore은 값은 null이다. 3)이후 nameStore필드에 "userA"값을 대입한다. 그  4)다음 다시 nameStore가 log에 출력된다. 그때 nameStore 필드에 값은 userA이다. 

     

    2.

    userA와 같은 과정이 반복된다. 다만 최초에 nameStore값은 null이 아니라 "userA"이다.

     

    동시성 문제발생 코드

    이번에는 sleep(100)을 설정해서 thread-A의 작업이 끝나기 전에 thread-B가 실행되도록 해보자

    참고로 FieldService.logic()메서드는 내부에 sleep(1000)으로 1초의 지연이 있다. 따라서 1초 이후에 호출하면 순서대로 실행할 수 있다. 다음에 설정할 100(ms)는 0.1초이기 때문에 thread-A의 작업이 끝나기 전에 thread-B가 실행된다.

    ...
    //sleep(2000); //동시성 문제 발생X
    sleep(100);  //동시성 문제 발생O
    ...

     

     

    thread-A의  작업이다 끝나지 않았는데 thread-B가 들어와서 작업을 시작한다. threadA가 nameStore에 대입한 값을조회 하지도 전에 threadB가 nameStore의 값을 "userB"로 바꿔 버리기 때무에 thread-A와 thread-B의 조회 값은 모두 "userB"가 된다. 

     

    • thread-A의 호출이 끝나면서 nameStore의 결과를 반환 받는데, 이때 nameStore는 앞의 2번에서 userB의 값으로 대체되었다 따라서 기대했던 userA의 값이 아니라 userB의 값이 반환된다.
    • thread-B의 호출이 끝나면서 nameStore의 결과인 userB를 반환받는다.

     

    동시성 문제

    결과적으로 Thread-A입장에서는 저장한 데이터와 조회한 데이터가 다른 문제가 발생한다. 이처럼 여러 쓰레드가 동시에 같은 인스턴스의 필드 값을 변경하면서 발생하는 문제를 동시성 문제라고 한다. 이런 동시성 문제는 여러 쓰레드가 같은 인스턴스의 필드에 접근해야 하기 때문에 트래픽이 적은 상황에서는 확율상 잘 나타나지 않고, 트래픽이 점점 많아 질 수록 발생한다. 특히 스프링 빈 처럼 싱글톤 객체의 필드를 변경하며 사용할 대 이러한 동시성 문제를 조심해야 한다.

     

    ➕참고
    이런 동시성 문제는 지역 변수에서는 발생하지 않는다. 지역 변수는 쓰레드마다 각각 다른 메모리에 할당된다. 동시성 문제가 발생하는 곳은 같은 인스턴스의 필드(주로 싱글톤에서 자주 발생), 또는 static같은 공용 필드에 접근할 때 발생한다. 동시성 문제는 값을 읽기만 하면 발생하지 않는다. 어디선가 값을 변경하기 때문에 발생

     

    그렇다면 지금처럼 싱글톤 객체의 필드를 사용하면서 동시성 문제를 해결하려면 어떻게 해야 할까? 다시 파라미터를 전달하는 방시으로 돌아가야 할까? → 이럴 때 사용하는 것이 바로 쓰레드 로컬

     

     

    [출처 - 스프링 핵심원리-고급편, 저 김영한]

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8

     

    스프링 핵심 원리 - 고급편 강의 - 인프런

    스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강

    www.inflearn.com

     

    댓글

Designed by Tistory.