본문 바로가기
JAVA

ThreadLocal 파고들기

by HCastle 2022. 5. 31.
반응형

Ver : Java SE 11

Module : java.base

Package : java.lang

 

public class ThreadLocal<T> extends Object

 

ThreadLocal 클래스는 스레드 독립적으로 사용 할 지역변수이다.

같은 스레드라면 어느 곳에서나 get, set method 를 통해 같은 변수를 불러오거나 설정할 수 있고,

스레드 독립적이므로 각 스레드가 갖고 있는 지역변수는 독립적이다.

 

ThreadLocal's Mehod
Modifier And Type Method Description
T get() 이 thread-local 변수의 현재 thread의 사본에 있는 값을 반환
protected T initialValue() 이 thread-local 변수에 대한 현재 thread의 초기 값을 반환
void remove() 이 thread-local 변수에 대한 현재 thread 값을 제거
void set(T value) 이 thread-local 변수의 현재 thread의 사본을 지정된 값으로 설정
static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) thread 지역 변수를 생성

위 메서드를 보면 이해했을 수도 있겠지만, ThreadLocal 을 이해하려면 Thread class 의 인스턴스 변수 threadLocals 를 알아야 한다.

Thread 클래스에는 threadLocals 라는 인스턴스 변수가 선언되어있고, ThreadLocal 클래스는 get() or set() method 를 통해 threadLocals 에 값을 저장하거나 읽어오는 방식으로 동작한다.

public class Thread implements Runnable {
    ...
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ...
}
public class ThreadLocal<T> {
    ...
    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // Thread t 의 threadLocals 변수를 반환한다.
        if (map != null) {
            map.set(this, value); // threadLocals 를 value 로 저장한다.
        } else {
            createMap(t, value); // threadLocals 이 null 일 경우 value 값을 갖고 있는 ThreadLocalMap 객체를 생성하여 저장한다.
        }
    }
    ...
}

 

threadLocals 변수는 package private 이며 ThreadLocal 클래스의 인스턴스가 생성될 때 마다 hash map 형태로 저장하기 때문에 static method 를 갖는 별도의 클래스를 생성하여 사용하여야 한다.

예시
class ThreadScore {
    private static final ThreadLocal<Integer> threadScore = new ThreadLocal<>();

    public static Integer get() {
        return threadScore.get();
    }

    public static void set(Integer score) {
        threadScore.set(score);
    }
}

 

ThreadLocal 클래스를 통해 직접 접근해 보았지만 같은 Thread 라고 하더라도 hashmap 형태로 저장하고 있기 때문에 동일한 변수를 가져올 수 없다.

    public void threadLocalDirect() {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        threadLocal.set(89);
        System.out.println(Thread.currentThread().getName() + " - threadLocalDirect > get:" + threadLocal.get());
        ThreadScore.set(91);
        System.out.println(Thread.currentThread().getName() + " - threadLocalDirect > get:" + threadLocal.get());
        System.out.println(Thread.currentThread().getName() + " - threadLocalDirect > ThreadScore.get:" + ThreadScore.get());
        threadLocalDirectSub();
    }

    public void threadLocalDirectSub() {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        System.out.println(Thread.currentThread().getName() + " - threadLocalDirectSub > get:" + threadLocal.get());
        System.out.println(Thread.currentThread().getName() + " - threadLocalDirectSub > ThreadScore.get:" + ThreadScore.get());
    }
    
    
-- result
main - threadLocalDirect > get:89
main - threadLocalDirect > get:89
main - threadLocalDirect > ThreadScore.get:91
main - threadLocalDirectSub > get:null
main - threadLocalDirectSub > ThreadScore.get:91

 

ThreadLocal 변수 사용이 끝난 후에는 remove() method 를 호출해 줘야 하는데, 웹 기반의 애플리케이션에서는 쓰레드를 재사용하기 위해서 ThreadPool 을 사용하는데, ThreadPool 을 사용하면 Thread 가 시작된 후에 그냥 끝나는 것이 아니기 때문에 remove() method 를 사용하여 값을 지워줘야지만 해당 thread 를 다음에 사용할 때 쓰레기 값이 들어 있지 않게 된다.

 

withInitial(Supplier supplier)

마지막으로 ThreadLocal 의 get() method 를 사용할 때 아직 설정된 값이 없을 경우(set() method 가 호출되기 전) 에 초기값을 supplier 를 통해 지정해도록 해주는 method 이다.

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> -1);
System.out.println(Thread.currentThread().getName() + " - threadLocalDirectSub > get:" + threadLocal.get());


-- result
main - threadLocalDirectSub > get:-1

 

반응형

댓글