Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

동시성 문제 - 예제 코드 본문

인프런/[인프런] 스프링 핵심 원리 - 고급

동시성 문제 - 예제 코드

Seung__ 2021. 11. 22. 14:06

1. Test에서 lombok 사용


  • dependancies에 추가.
    //테스트에서 lombok 사용
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'

 

 

 

2.  FieldService


  • FieldService는 logic() 메서드를 제공한다.
  • name을 받으면  name sotre에 저장 후 값을 반환한다.
  • name sotre에 저장하는데 1초가 걸린다고 가정.
@Slf4j
public class FieldService {

    private String nameStore;

    public String logic(String name){
        log.info("저장 name={} --> nameStore={}", name, nameStore);
        nameStore = name;
        sleep(1000);
        log.info("조회 nameStore={}", nameStore);
        return nameStore;
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

3. FieldServiceTest - 순차적 실행.


  • 여러 Thread가 경쟁하는 상황을 만들기 전에 Thread가 순차적으로 실행되는 상황을 보자.
    • threadA실행 후 2초의 sleep을 두어 threadA가 완전히 수행할 시간을 둠.
  • 참고) Thread 생성 방법      
- Ruunable 인터페이스의 run() 구현.
- Thread는 Thread 클래스를 상속 받거나, Runnable 인터페이스를 구현하거나,
- Runnable 인터페이스 구현 후 Thread에 주입하는 방식으로 사용 가능.
https://bsh-developer.tistory.com/584
  • threadA, threadB가 순차적으로 실행 될 수 있도록 조건을 부여함.
@Slf4j
public class FieldServiceTest {

    private FieldService fieldService = new FieldService();

    @Test
    void field(){
        log.info("main start");

        /*
        Runnable runnable= new Runnable() {
            @Override
            public void run() {

            }
        }*/

        //Ruunable 인터페이스의 run() 구현.
        //Thread는 Thread 클래스를 상속 받거나, Runnable 인터페이스를 구현하거나,
        //Runnable 인터페이스 구현 후 Thread에 주입하는 방식으로 사용 가능.
        Runnable userA = () -> {
            fieldService.logic("userA");
        };

        Runnable userB = () -> {
            fieldService.logic("userB");
        };

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A");
        Thread threadB = new Thread(userB);
        threadA.setName("thread-B");

        threadA.start();
        sleep(2000); //동시성 문제 발생 안하는 조건.
        threadB.start();

        sleep(2000); //메인 Thread 종료 대기
        log.info("main exit");

    }

    private void sleep(int millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
[main] - main start
[thread-A] - 저장 name=userA --> nameStore=null
[thread-A] - 조회 nameStore=userA
[Thread-B] - 저장 name=userB --> nameStore=userA
[Thread-B] - 조회 nameStore=userB
[main] - main exit

  • 실행 결과
  • Thread-A 는 userA 를 nameStore 에 저장
  • Thread-A 는 userA 를 nameStore 에서 조회
  • Thread-B 는 userB 를 nameStore 에 저장
  • Thread-B 는 userB 를 nameStore 에서 조회

 

 

4. FieldServiceTest - 동시성 문제 발생.


  • 여러 Thread가 경쟁하는 상황을 만들자.
    • threadA실행 후 0.1초의 sleep을 두어 threadA가 완전히 수행되기 전에 theadB가 실행되도록 함.
@Slf4j
public class FieldServiceTest {

    private FieldService fieldService = new FieldService();

    @Test
    void field(){
        log.info("main start");

        /*
        Runnable runnable= new Runnable() {
            @Override
            public void run() {

            }
        }*/

        //Ruunable 인터페이스의 run() 구현.
        //Thread는 Thread 클래스를 상속 받거나, Runnable 인터페이스를 구현하거나,
        //Runnable 인터페이스 구현 후 Thread에 주입하는 방식으로 사용 가능.
        Runnable userA = () -> {
            fieldService.logic("userA");
        };

        Runnable userB = () -> {
            fieldService.logic("userB");
        };

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A");
        Thread threadB = new Thread(userB);
        threadA.setName("thread-B");

        threadA.start();
        //sleep(2000); //동시성 문제 발생 안하는 조건.
        sleep(100); //동시성 문제 발생하는 조건.
        threadB.start();

        sleep(2000); //메인 Thread 종료 대기
        log.info("main exit");

    }

    private void sleep(int millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  •  threadA가 조회하기 전에 threaB가 userB를 저장해버림.ㅋㅋㅋ
  • 그래서 threadA는 userB를 조회하게됨.
[main] - main start
[thread-A] -저장 name=userA --> nameStore=null
[thread-B] -저장 name=userB --> nameStore=userA
[thread-A] -조회 nameStore=userB
[thread-B] -조회 nameStore=userB
[main] - main exit
  • theadA가 nameStore에 userA 저장

  • +0.1초 후 theadB가 nameStore에 userB 저장

  • +1.0초 후 theadA가 nameStore에 저장된 정보(userB) 조회
  • +1.1초 후 theadB가 nameStore에 저장된 정보(userB) 조회

 

 

 

 

5. 동시성 문제


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

 

 

6. 참고


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

 

 

7. GitHub : 211122 Concurrency Issue


 

GitHub - bsh6463/Spring_Advanced: initial

initial. Contribute to bsh6463/Spring_Advanced development by creating an account on GitHub.

github.com

 

'인프런 > [인프런] 스프링 핵심 원리 - 고급' 카테고리의 다른 글

ThreadLocal - 예제  (0) 2021.11.22
Thread Local - 소개  (0) 2021.11.22
필드 동기화 - 동시성 문제  (0) 2021.11.20
필드 동기화 - 적용  (0) 2021.11.20
필드 동기화 - 개발  (0) 2021.11.20
Comments