필드 동기화 - 동시성 문제

zhzkzhffk·2022년 5월 20일
0

이 게시글은 인프런 김영한님 강의를 듣고 복습하며 기록한 글입니다.


서론

스프링 빈에 등록된 객체는 싱글톤이다. 등록된 객체의 인스턴스가 애플리케이션에 딱 하나 존재한다는 뜻이다. 이렇게 하나만 있는 인스턴스의 필드(공유자원)를 여러 스레드가 동시에 접근하면 문제가 발생한다. 이는 동시성 문제이다.

**공유자원**

동시성 문제가 발생하는 곳은 같은 인스턴스의 필드 또는 static같은 공유 필드에 접근할 때 발생한다.
동시성 문제는 값을 읽기만 하면 발생하지 않는다. 다만 값을 변경하는 쓰기 과정에서 발생한다.

동시성 문제

동시성 문제는 멀티 스레드 환경에서 발생한다. 위의 그림처럼 여러 스레드가 동시에 공유 자원에 접근해서 수정을 하는 경우 발생한다. 동시에 값을 수정했을 때, 각 스레드가 저장한 데이터와 조회한 데이터가 다른 문제가 발생한다.

동시성 문제는 공유 변수에 여러 스레드가 동시에 수정을 하면서 일어나는 일이기 때문에 수정하는 공간을 분리해주면 된다. → 스레드 로컬

ThreadLocal

각 스레드 별로 별도의 공간이 따로 할당되고, 각 스레드마다 별도의 내부 저장소만 접근할 수 있다. 따라서 같은 인스턴스의 스레드 로컬 필드에 접근해도 문제가 생기지 않는다.

ThreadLocal.java

  • ThreadLocals rely on per-thread linear-probe hash maps attached to each thread.
@Slf4j
public class ThreadLocalService {
    private ThreadLocal<String> nameStore = new ThreadLocal<>();

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

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

    }
}
@Slf4j
public class ThreadLocalServiceTest {

    private ThreadLocalService service = new ThreadLocalService();

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

        Runnable userA = () -> {
            service.logic("userA");
        };

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

        Thread threadA = new Thread(userA);
        threadA.setName("thread-A");
        Thread threadB = new Thread(userB);
        threadB.setName("thread-B");
        threadA.start();
        sleep(100);     //동시성 문제 발생O
        threadB.start();

        sleep(3000);    // 메인 스레드 종료 대기
        log.info("main exit");
    }

    private void sleep(int millis) {

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

**ThreadLocal 사용시, 주의 사항**

쓰레드 로컬의 값을 사용 후 제거하지 않고 그냥 두면 WAS(톰캣)처럼 쓰레드 풀을 사용하는 경우에 심각한 문제가 발생할 수 있다.

  1. 많은 스레드가 일하고 있는 상황에서 스레드가 계속 만들어지고 지워지지 않는다고 생각해보면, 메모리 누수가 발생한다.

  2. 기존의 스레드가 사용했던 스레드 로컬 정보를 사용하고 ThreadLocal.remove()를 호출을 생략하면 스레드 로컬에 정보가 남아있다.(스레드 풀: 스레드는 비싸기에 스레드 풀을 두고 재사용한다.) 이는 기존의 스레드에 남아있던 데이터를 다른 스레드가 데이터를 볼 수 있는 문제가 발생한다.

profile
Backend Developer

0개의 댓글