Please enable JavaScript to view the comments powered by Disqus.ThreadLocal이란 무엇인가? JNI를 열어서 확인해보자
Search
🙋‍♂️

ThreadLocal이란 무엇인가? JNI를 열어서 확인해보자

태그
thread
Java
Kotlin
JVM
공개여부
작성일자
2023/10/02
Thread 내부의 값, 값을 갖고 있는 thread safe 기법을 적용할 수 있도록 도와주는 것이 ThreadLocal 이다.
이 ThreadLocal에 대해 살짝 파헤쳐보고자 한다.
호출하는 thread마다 다른 값을 사용할 수 있도록 get(), set() 함수가 있다.
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(); }
Java
복사
ThreadLocal 의 get 함수
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } }
Java
복사
ThreadLocal의 set 함수
위 함수들을 확인해 보면 Thread.currentThread() 함수를 통해서 현재 실행중인 thread에 접근하는 Thread의 기능이 있다.

Thread.currentThread()의 JNI 구현

Thread.currentThread() 함수를 통해서 현재 실행중인 thread에 접근하는 JNI 코드를 확인해보자.
코드는 openJDK에서 확인할 수 있다.
/** * Returns a reference to the currently executing thread object. * * @return the currently executing thread. */@IntrinsicCandidate public static native Thread currentThread();
Java
복사
이 기능은 JNI에서 제공한다. 매우 저수준의 기능으로 볼 수 있다.
// Method entry for java.lang.Thread.currentThread address TemplateInterpreterGenerator::generate_currentThread() { address entry_point = __ pc(); __ ldr(r0, Address(rthread, JavaThread::vthread_offset())); __ resolve_oop_handle(r0, rscratch1, rscratch2); __ ret(lr); return entry_point; }
C++
복사
JDK를 열어보면 이러한 코드가 있다.
상세히 살펴보자.
address entry_point = __ pc();
C++
복사
method의 시작점을 나타내는 주소를 가져온다. 현재 program counter의 값을 반환하며 기계어 코드의 시작 주소를 반환한다.
__ ldr(r0, Address(rthread, JavaThread::vthread_offset()));
C++
복사
ldr은 Load Register의 약제로, 메모리에서 값을 불러와 레지스터에 저장하는 ARM 어셈블리 명령어 이다. 이 명령어는 JavaThread::vthread_offset() 에서 지정된 오프셋의 메모리 값을 r0 레지스터에 로드한다.
__ resolve_oop_handle(r0, rscratch1, rscratch2);
C++
복사
r0 에 저장된 핸들을 resolve 한다. JVM 내부에서 객체 참조를 안전하게 처리하기 위한 cpp의 매커니즘이다.
__ ret(lr);
C++
복사
ret는 return의 양자로 lr(link register)에 저장된 주소로 반환하는 ARM 어셈블리 명령어 이다. 이는 generate_currentThread 메서드의 실행을 종료하고 호출자에게 제어를 반환한다.
return entry_point;
C++
복사
메서드는 처음에 저장된 entry_point 주소를 반환하여 생성된 기계어 코드의 시작점을 나타낸다.
이러한 코드는 Java method의 호출을 빠르게 처리하기 위해 사용된다.

Singleton과 ThreadLocal

ThreadLocal 변수이러한 상황일 때 사용한다.
변경 가능한 싱글턴
전역 변수 등을 기반으로 설계되어 있는 구조에서 변수가 임의로 공유되는 상황을 막기 위해 사용하는 경우가 많다.
ObjectMapper 를 thread 마다 하나씩 생성한다고 가정해보며 아래와 같이 구성할 수 있다.
object ObjectMapperHolder { private val threadLocal = ThreadLocal.withInitial { ObjectMapper() } val mapper: ObjectMapper get() = threadLocal.get() }
Kotlin
복사

ThreadLocal의 내부 구현

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(); }
Java
복사
ThreadLocal 의 get 함수
이 함수에서 보면 현재 thread를 찾아와 그 thread를 map으로 get 하는 기능이 있다.
즉, ThreadLocal 은 내부적으로 Map<Thread, T> 로 구성된 것이다.
물론 실제 Map 은 아니고, ThreadLocalMap 이라하는 customized map을 사용한다.
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. * */ static class ThreadLocalMap {
Java
복사
ThreadLocalMap은 ThreadLocal 안에 static inner class로 정의되어 있다.
Thread 별 값은 실제로 Thread 객체 자체에 저장되어 있으며, thread가 종료되면 값으로 할당된 부분도 GC되어 삭제된다.
이렇게 편리하긴 하지만, 전역 변수가 아니면서도 전역 변수처럼 동작하기 때문에 결합도가 증가하며 성능 오버헤드가 발생하는 등 자세히 학습하고 사용해야 한다.