본문 바로가기

전체보기/Java

[Java]ThreadLocal이란?

ThreadLocal은 이름에서 알 수 있듯이

각 thread 내부에서 사용되는 지역변수를 관리할 수 있는 클래스입니다.

 

설명을 돕기위해 간단한 코드를 가져와봤습니다.

 

public class ThreadLocalTest {
    private static ThreadLocal<Double> local = ThreadLocal.withInitial(Math::random);

    public Double get(){
        return local.get();
    }
}

public class Execute {
    public static void main(String[] args) {
        new Thread(() -> {
            ThreadLocalTest t = new ThreadLocalTest();
            System.out.println("first thread: " + t.get());
        }).start();

        new Thread(() -> {
            ThreadLocalTest t = new ThreadLocalTest();
            System.out.println("second thread: " + t.get());
        }).start();
    }
}

 

위의 코드 실행 결과는 다음과 같습니다.

second thread: 0.599826108937703
first thread: 0.6325847525515514

중요한 것은 thread의 실행순서나 출력된 값이 아니라

첫번째 thread의 값과 두번째 thread의 값이 다르게 출력되었다는 것 입니다.

 

어떻게 위와 같은 결과가 나올 수 있었는지 ThreadLocal 내부를 뜯어보겠습니다.

//ThreadLocal.java
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
    
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();
}

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

//ThreadLocal.ThreadLocalMap
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

ThreadLocal의 값을 설정하거나 조회할 때는 각각 set, get 메소드를 활용하며

위의 코드는 설명을 위해 일부를 가져온 것입니다.

 

각 Thread는 ThreadLocalMap 타입의 threadLocals라는 필드를 가지고있으며

ThreadLocalMap에는 Entry 타입 배열인 table이,

Entry에는 ThreadLocal과 우리가 저장하고자하는 값(예제코드에서는 Math::random)이 포함되어있습니다.

그림으로 표현하면 위와 같습니다.

 

threadLocals는 여러 Thread에서도 독립적으로 존재할 수 있습니다.

그렇기 때문에 위의 예제 코드에서 첫번째 Thread와 두번째 Thread에서의 값이 다르게 나온것입니다.

 

또한 threadLocals의 요소들은 ThreadLocal로 구분할 수 있기 때문에

하나의 Thread 안에 여러 ThreadLocal을 정의해도 문제가 없습니다.

(물론 threadLocals의 크기보다 많은 ThreadLocal을 정의하거나 해싱값(i)이 겹치면 문제가 되지만 이 부분은 기회가 된다면 다른 글에서 다뤄보도록 하겠습니다.)

 

단, ThreadLocal을 사용하기 위해 주의해야하는 점은

static ThreadLocal<?> local;

위와 같이 static으로 정의해야한다는 점입니다.

public static void main(String[] args) {
    new Thread(() -> {
        ThreadLocalTest t = new ThreadLocalTest();
        System.out.println("first object: " + t.get());

	ThreadLocalTest t2 = new ThreadLocalTest();
        System.out.println("second object: "  + t2.get());
    }).start();
}

public class ThreadLocalTest {
    private ThreadLocal<Double> local = ThreadLocal.withInitial(Math::random);

    public Double get(){
        return local.get();
    }
}
first object: 0.2471947152169739
second object: 0.28062552626555337

그렇지 않으면 위와 같이 하나의 Thread 안에서 값이 다르게 나오기 때문입니다.

 

위와 같은 결과가 나온 이유는

앞서 설명했듯이 threadLocals의 값들은 ThreadLocal 객체로 구분되는데

위의 코드에서는 ThreadLocalTest 객체가 생성될 때마다

ThreadLocal 객체도 생성되기 때문에

Thread는 둘을 각각 threadLocals에 저장하게 됩니다. 

 

여기까지 ThreadLocal의 대략적인 설명이었습니다.

혹시 궁금하거나 틀린 부분이 있다면 언제든 댓글로 알려주세요.

감사합니다!!

반응형