[Spring] 동시성 문제와 ThreadLocal

벼랑 끝 코딩·2025년 3월 19일

Spring

목록 보기
4/16

Spring을 시작하면 스프링 컨테이너가 생성되고
Spring Bean으로 등록 후 의존 관계가 주입된다는 점을 배웠다.
그리고 Spring Bean에 등록된 초기화 메서드가 실행되고,
종료 전에는 종료 메서드가 실행된 후 종료되며
이 과정에서 Spring Bean의 생명 주기를 설정할 수 있는 Bean Scope에 대해 학습했다.

Spring은 기본적으로 Bean을 시작부터 종료까지인 Singleton으로 생명주기를 관리한다.
Singleton의 또 다른 특징으로는 인스턴스를 1개만 생성된다는 것을 알았다.

인스턴스가 1개만 생성되는 경우 내가 저장한 값이 다른 사람과 공유되어
다른 사람에 의해 변경될 수 있는 위험성이 있었는데
이러한 동시성 문제를 막기 위해 필드를 선언하면 안된다는 점이 중요했다.

그렇다면 모든 Bean은 필드 없이 생성해야 할까?
사실 그것은 불가능에 가깝다.
불가능에 가깝다면 해결책이 있는 법.
이번에는 Spring에서 동시성 문제를 막기 위한 ThreadLocal에 대해 알아보자.

동시성 문제

@Service
class Service {
	
    String name;
	
    // 코드
}

class Client {
	
    private final Service service;
    
    @Autowired
    public Client(Service service) {
    	this.service = service
    }
}

위 코드는 name이라는 필드를 보유하고 있다.
멀티 스레드 환경에서 여러 Client 인스턴스가 생성된다고 하더라도
Spring은 Singleton으로 생성하여 Service 객체를 1개만 생성하기 때문에,
여러 Client 객체는 Service의 name을 공유한다.

만약 Client1이 name을 설정하는 기능으로 "김클라"라고 이름을 설정했어도,
이후 Client2가 접속하여 name을 "이클라"라고 설정한다면,
최종적으로 name에는 "이클라"가 저장되는 것이다.
Client1은 자신이 저장한 "김클라"의 이름이 없어져 당황스러운 상황이 일어난다.

ThreadLocal

Spring에서는 이러한 동시성 문제를 막기 위해 ThreadLocal을 사용할 수 있다.

@Service
class Service {
	
    // ** ThreadLocal 사용 **
    ThreadLocal<String> name = new ThreadLocal<>();
    
    // 코드
}

class Client {
	
    private final Service service;
    
    @Autowired
    public Client(Service service) {
    	this.service = service;
    }
    
    public void serviceMethod() {
    	name.set("김클라");  // ** ThreadLocal.set() **
        String clientName = name.get();  // ** ThreadLocal.get() **
        name.remove();  // ** ThreadLocal.remove() **
    }

ThreadLocal은 멀티스레드 환경에서 여러 Client 객체가 생성되어 접근해도,
각각 다른 보관소에 값을 저장하기 때문에
Singleton으로 Bean을 관리하면서도 안전하게 변수를 사용할 수 있다.

set() 메서드를 사용하여 별도의 보관소에 값을 저장할 수 있고
get() 메서드를 사용하여 저장한 값을 조회할 수 있다.

ThreadLocal.remove()

ThreadLocal은 사용이 끝나면 반드시 remove() 메서드로 값을 제거해야 한다.

Client1이 ThreadLocal을 사용한 후,
remove() 메서드를 호출하지 않고 종료했다고 가정하자.
이후 Client2가 ThreadLocal을 사용하여 값을 조회하면
Client1이 제거하지 않은 값이 남아있을 수 있다.

그렇기 때문에 ThreadLocal은 사용 후 반드시 remove()로 값을 제거해주어야 한다!

마무리

Spring에서 Singleton으로 Bean을 관리하기 때문에 발생하는 동시성 문제의
해결책으로 ThreadLocal을 알아봤다.
Spring 객체에서 필드가 필요하다면 ThreadLocal의 사용을 고려하자.
사용 후에는 반드시 remove() 메서드를 호출하여 정리하는 것도 잊지 말자.

profile
복습에 대한 비판과 지적을 부탁드립니다

0개의 댓글