일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Kubernetes
- programmers
- 자바
- map
- IntelliJ
- Oracle
- 알고리즘
- dabase
- cd
- 뷰
- 프로그래머스
- superBuilder
- ORM
- JPA
- CI
- k8s
- 해시맵
- docker
- 코딩테스트연습
- SpringMVC
- DevOps
- Spring
- hibernate
- builder-pattern
- Vue
- CI/CD
- vuejs
- Di
- java
- CKA
- Today
- Total
문홍의 공부장
[Java/Spring] 비동기 처리 시 ThreadContext 를 관리하는 방법 본문
웹 애플리케이션을 개발하다 보면 하나의 클라이언트 요청에 대해 애플리케이션 전반에 걸친 특정 Context를 유지
해야 할 필요성이 생긴다. 대표적으로 로깅 정보나 사용자 정보가 있다. 이 때 흔히 사용하는 방안은 ThreadLocal을 활용
하는 것이다.
스프링에서 Web Request
가 오게 되면 하나의 쓰레드를 할당해서 해당 작업을 처리하게 된다. 이때 Thread
에 대한 정보를 ThreadLocal
에 저장하게 되면 해당 작업이 끝날 때 까지 모든 상황에서 context
를 유지하고 저장하고 찾아볼 수 있다.
ThreadLocal은 쓰레드의 로컬 컨텍스트 변수로 Thread 가 존재하는 한 계속해서 남아 있는 변수이다. 작업 요청이 들어왔을때 하나의 쓰레드가 생성이 되고 작업이 끝나면 쓰레드가 없어진다고 할 때, 쓰레드가 살아있을 동안에 계속 유지되는 변수이다. 즉 쓰레드가 생성되어 있는 동안에, 쓰레드 안에서 계속 참고해서 쓸수 있는 쓰레드 범위의 글로벌 변수라고 볼 수 있다.
하지만 ThreadLocal은 이름 그대로 Thread별로 값을 유지하기 때문에 비동기 호출 시 해당 값이 유지되지 않는다. 비동기 처리를 위해 TaskExecutor
를 활용하기 시작하면 해당 executor
는 새로운 Thread
를 생성한다. 즉, 기존 쓰레드의 context
가 넘어가지 않고 새로운(빈) 쓰레드를 사용하게 되는 것이다.
비동기 처리 시 기존 쓰레드의 값을 유지하려면 어떻게 해야할까?
Spring 4.3
이상부터 제공되는 TaskDecorator
를 이용해서 비동기처리하는 taskExecutor
생성 시 커스터마이징이 가능하다.
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UserIdHolder {
private final static ThreadLocal<Long> USER_ID_HOLDER = new ThreadLocal<>();
public static Long getUserId() {
return USER_ID_HOLDER.get();
}
public static void setUserId(Long userId) {
USER_ID_HOLDER.set(userId);
}
public static void clear() {
USER_ID_HOLDER.remove();
}
}
---
public class ClonedTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable task) {
long userId = UserIdHolder.getUserId(); // 기존 thread에서 userId를 가져온다.
return () -> {
try {
UserIdHolder.setUserId(userId); // 새로운 thread에서 가져온 userId를 세팅한다.
runnable.run();
} finally {
UserIdHolder.clear(); // 새로운 thread 작업이 완료되면 ThreadLocal 값을 초기화한다.
}
};
}
}
---
@Bean(name = "test")
public Executor testThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(5000);
executor.setThreadNamePrefix("default-thread-");
executor.setTaskDecorator(new ClonedTaskDecorator());
executor.initialize();
return executor;
}
References.
'개발 > Spring' 카테고리의 다른 글
[Java/Spring] Event publish & Listener (0) | 2024.03.09 |
---|---|
[Spring] ResponseBodyAdvice 를 이용하여 ControllerAdvice 활용하기 (1) | 2024.01.24 |
@Primary 와 @Qualifier (2) - 활용편: 2개 이상의 데이터베이스 연결하기 (0) | 2023.05.31 |
[Spring] @Primary 와 @Qualifier 비교하고 이해하기 (0) | 2023.04.18 |
[Spring/Java] 회원가입 이메일 인증 구현하기 (13) | 2020.03.31 |