회사에서 일하면서 처음마주친 이녀석을 제 개인프로젝트에서 한번 사용해보고자 포스팅을 작성하였습니다.
팀 프로젝트 및 개인 학습을 병행하면서 ThreadLocal을 활용한적도, 하는것을 본적도 없던지라 저에겐 생소한 자바 클래스였는데요
잘 활용하면 기존 제가 느꼈던 불편함을 해소하면서 장점을 취할 수 있을것같아 정리해보겠습니다.
ThreadLocal은 스레드마다 따로 값을 저장하고 꺼내게 해주는 객체다. 같은 ThreadLocal 객체 하나를 여러 스레드가 함께 써도, get()을 호출하면 각 스레드는 자기가 넣은 값만 돌려받는다.
비유를 하자면 Thread마다 가방을 메고 있는 느낌이 정확하다고 생각한다. 실제로 Spring의 트랜잭션 관리와 SecurityContextHolder도 이 방식으로 동작한다.
이제 실제 동작을 코드로 확인해보고 분석해보겠다!
private static final ThreadLocal<Integer> tl = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
tl.set(0); // main 스레드 - 스레드로컬 값 설정
System.out.println(Thread.currentThread().getName() + " 시작 get() = " + tl.get());
// Th-A, Th-B 스레드 실행
Thread thA = new Thread(task(100), "Th-A");
Thread thB = new Thread(task(200), "Th-B");
thA.start();
thB.start();
thA.join();
thB.join();
// Main 스레드 - 스레드로컬 값 출력
System.out.println(Thread.currentThread().getName() + " 끝 get() = " + tl.get());
}
private static Runnable task(int value) {
return () -> {
String name = Thread.currentThread().getName();
System.out.println(name + " 시작 get() = " + tl.get());
tl.set(value); // 스레드 로컬 값 설정
System.out.println(name + " 종료 get() = " + tl.get());
//tl.remove();
};
}
각 스레드마다 데이터를 독립적으로 관리 한다는 것을 확인하였다. 어떻게 구현되어있을까?
--- ThreadLocal.java --------------------------------------------------------
public T get() {
return get(Thread.currentThread());
}
private T get(Thread t) {
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(t);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
-----------------------------------------------------------------------------
--- Thread.java -------------------------------------------------------------
/*
* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals;
-----------------------------------------------------------------------------
아래와 같은 과정을 거쳐 get(); 메서드가 작동합니다.
여기서 핵심은 각각의 Thread 클래스가 ThreadLocalMap을 관리한다. 하지만 의문점이 생겼다, 만약 내가 설계자라면 ThreadLocal에 ThreadLocalMap 전역 필드를 선언하고 thread기반 key조회를 하는 형식으로 개발했을 것 같기때문이다.
히스토리 확인 결과 JDK1.2에서 내 생각과 동일하게 개발이 되었고, JDK1.3에서는 현 상태로 구조가 변경됐다.
하지만 JDK1.3에서 이 구조를 뒤집어(ThreadLocal마다 맵 → Thread마다 맵) 지금까지 이어져 왔다. 히스토리를 보고 알 수 있는 의도는 다음과 같다. (https://bugs.openjdk.org/browse/JDK-4252910) 참고
remove()가 필요하다.추가로 덧붙이자면 하나의 스레드가 여러 ThreadLocal을 사용한다는 확장성 관점에서도 필요한 변경이였다고 생각이든다 ㅎㅎ;;