본문 바로가기

Language/Java

스레드 로컬(ThreadLocal)

ThreadLocal

ThreadLocal은 클래스로, 이 스레드 로컬을 통해 각 스레드 별로 필요한 정보를 저장할 수 있다. 각 스레드는 자신만의 스레드 로컬을 가질 수 있으며, 다른 스레드의 스레드 로컬에는 접근할 수 없다. 각 스레드 별로 별도의 스레드 로컬을 갖기 때문에 멀티스레드 환경에서 공유 자원에 대한 동기화 문제를 피하고, 각 스레드가 자신의 상태를 안전하게 유지할 수 있다.

ThreadLocalMap

ThreadLocalMap은 ThreadLocal의 정적 내부 클래스이다. 모두 private으로 구성되어 있어서 외부에서 접근 가능한 메서드가 없다. 

 

Thread 클래스는 ThreadLocalMap을 멤버로 갖는다. 즉, 각 스레드마다 ThreadLocalMap을 갖는다.

public class Thread implements Runnable {
   
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    // ...
}

 

ThreadLocal의 set과 get 메서드를 보면, 현재 스레드의 ThreadLocalMap 인스턴스를 가져와서 해당 자료구조에 값을 저장하고 조회하는 것을 알 수 있다. 그리고 set 메서드를 살펴보면 key-value에서 ThreadLocal가 key인 것을 알 수 있다.

public class ThreadLocal<T> {

	// ...
    
	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();
    }

	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

 

그리고 ThreadLocalMap의 내부를 좀 더 살펴보면, Entry 배열을 통해(Entry[]) 특정 스레드의 ThreadLocal들을 관리한다. 그리고 key 매개변수로 넘어온 ThreadLocal 객체의 주소값을 통해 배열에 value를 가진 Entry를 저장한다.

        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.refersTo(key)) {
                    e.value = value;
                    return;
                }

                if (e.refersTo(null)) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 

정리하면 다음과 같다.

  • ThreadLocal 인스턴스는 내부적으로 현재 스레드의 ThreadLocalMap 인스턴스를 가져온다.
  • ThreadLocalMap은 배열로 관리되는데 이 곳에 ThreadLocal 값을 저장하고, 이곳에서 ThreadLoal 값을 가져온다.
  • 즉, 각 스레드마다 ThreadLocalMap을 갖는다. 그리고 각 ThreadLocal마다 자신의 스레드의 ThreadLocalMap 중 특정 위치(ThreadLocal 객체의 해시 코드를 키 값으로 접근하여)에 접근할 수 있게 되고, 그 곳에 자신의 값이 저장되어 있다.


Reference