ThreadLocal은 Java에서 각 쓰레드별로 독립적으로 변수를 관리하기 위한 객체 이다.
Java의 쓰레드 class 정의를 보면 아래와 같이 쓰레드 별로 ThreadLocals 변수를 가지고 있는것을 볼 수 있다.
java.lang.Thread
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal의 Get 메소드를 잠시 살펴보면 현재 currentThread 객체를 가져와서 해당 객체에 대한 값을 끄집어 내게 되어있다. 쓰레드별로 key를 관리하면서 독립적인 변수 설정이 되는 원리이다.
java.lang.Thread
public class ThreadLocal<T> {
...
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
사용법은 다음과 같다 ContextHolder 객체를 싱글턴으로 관리하면서 사용하면 된다.
/**
* description : Context Holder
*/
public class ThreadLocalContextHolder {
private static final ThreadLocal<ThreadLocalContext> contextHolder = new ThreadLocal<>();
/**
* @name : setContext
* @param : context
* @description : ThreadLocalContextHolder를 셋팅한다.
*/
public static void setContext(ThreadLocalContext context) {
contextHolder.set(context);
}
/**
* @name : getContext
* @return : ThreadLocalContextHolder
* @description : ThreadLocalContextHolder를 얻는다.
*/
public static ThreadLocalContext getContext() {
ThreadLocalContext context = contextHolder.get();
if (context == null) {
context = new ThreadLocalContext();
contextHolder.set(context);
}
return context;
}
/**
* @name : clearContext
* @description : context를 clear한다.
*/
public static void clearContext() {
contextHolder.remove();
}
}
ThreadLocal 사용시 주의점
사용법은 간단하지만 꼭 주의해야할 점이 있다. ThreadLocalContext를 사용후에는 반납시 자동으로 값이 초기화되지 않기 때문에 Thread Pool 처럼 전체 Pool안에서 재활용하며 application이 구동될 경우 신규 context 생성 시 이전 쓰레기값이 채워진채로 재사용될 수가 있다.
ThreadLocal 해제시 clear를 안했을 경우에 어떻게 되는지 쉽게 확인해 볼 수 있다.
우선 재현을 쉽게 하기 위해 아래와 같이 Spring boot로 백엔드 구동시 tomcat의 threads max를 작게 설정한다. (아래는 3개로 설정)
src/main/resources/application-local_auth.yml
server:
port: 8091
address: xxx.30.1.30
servlet:
session:
tracking-modes: cookie
cookie:
secure: true
nfs: c:/Workspace/nfs
tomcat:
basedir: c:/Workspace/tomcat
threads:
max: 3
min-spare: 3
간단하게 위 ThreadLocal 변수를 생성할 Contoller api를 만들어본다.
@RequestMapping(value = "/test/session", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<ResultDVO> createTestSession(HttpServletRequest request) throws Exception{
HttpSession session = request.getSession(true);
session.setAttribute("TEST_DATA", System.currentTimeMillis());
ThreadLocalContext context = ThreadLocalContextHolder.getContext();
UserDVO user = new UserDVO();
user.setLoginId("test");
user.setUserId("test");
context.setUser(user);
return new ResponseEntity<>(new ResultDVO(), HttpStatus.OK);
}
Swagger-ui나 Postman 등의 Tool로 해당 API를 연속 호출해 본다. Thread pool이 3개 이므로 3번까지는 context에 값이 null인채로 할당이 된다. 그러나 4번째부터는 쓰레드 생성시 context에 이전 쓰레기 값이 채워져서 리턴되는것을 볼 수 있다.
ThreadLocal 변수 clear 방법
변수 clear 방법은 간단한데 interceptor의 afterCompletion 메소드를 override하고 그안에서 clearContext()를 불러주어 해당 Thread에서 할당한 값들을 해제하여 주면 된다.
public class CommInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ThreadLocalContextHolder.clearContext();
}
}
위와 같이 처리를 한 후에는 api를 계속 호출해봐도 쓰레기 값이 채워져서 return되지 않음을 볼 수 있다.
-- The End --
'Programming Language > Java' 카테고리의 다른 글
Java group key class로 stream groupingBy 사용하기 (1) | 2023.03.18 |
---|---|
[Java] 제네릭(Generic) 분석 (0) | 2022.03.12 |