ThreadLocal이란?

김강욱·2024년 4월 17일

Java

목록 보기
2/2
post-thumbnail

1. ThreadLocal?? Thread와 Local의 합친 말인가??

오늘은 ThreadLocal에 대해서 알아볼까 합니다.

ThreadLocal의 이름을 보고 유추해보자면 Thread의 지역(Local) 어쩌고... 하는 느낌이니까? Thread마다 가질 수 있는 지역 변수 개념이 아닐까 하는 생각이 듭니다.

너무 억지로 끼워맞추는 느낌이 들었네요 ㅎㅎ

자바에서 스레드(Thread)마다 독립적인 변수를 가질 수 있게 해주는 변수를 ThreadLocal 이라고 합니다.

ThreadLocal은 JDK 1.2부터 제공된 오래된 클래스입니다.

이 클래스를 활용하면 스레드 단위로 로컬 변수를 사용할 수 있기 때문에 마치 전역 변수처럼 여러 메서드에서 활용할 수 있습니다.

일반적인 전역 변수들은 모든 스레드에서 공유되어 동시성 문제를 야기할 수 있습니다만 ThreadLocal을 사용하면 각 스레드는 자신만의 변수 복사본을 가지게 되므로 다른 스레드와 변수를 공유하지 않게 됩니다.

이를 통해 각 스레드는 다른 스레드의 영향을 받지 않고 해당 데이터에 접근하고 수정할 수 있어 스레드 안전성을 보장할 수 있다는 장점이 있습니다.

예를 들어, 스프링 컨테이너에서 생성된 스프링 빈에서는 싱글톤으로 만들어지기 때문에 객체의 속성값을 가지면 멀티 스레드 환경에서 변수의 공유 문제가 발생하기 때문에 무상태로 관리하는 것이 좋다고 알고 있습니다.

그럼에도 스프링 빈에서 각자의 요청에 따라 다른 상태값을 저장하고 싶을 때 ThreadLocal 변수를 이용하면 해결할 수 있습니다.

하지만 무엇이든 장점이 있다면 단점이 있겠죠?

ThreadLocal는 메모리 누수의 주범이 됨으로 주의해서 사용해야 합니다.

Threadpool 환경에서 ThreadLocal을 사용하는 경우 변수에 보관된 데이터 사용이 끝나면 반드시 해당 데이터를 삭제 해주어야 합니다. 그렇지 않을 경우 재사용 되는 스레드가 올바르지 않은 데이터를 참조할 수도 있습니다.

2. ThreadLocal 구성

ThreadLocal과 연관된 클래스들의 구성에 대해서 알아보도록 하겠습니다.

ThreadLocalMap


ThreadLocalMap은 ThreadLocal 클래스의 정적 내부 클래스입니다. 내부적으로 해시 테이블 정보를 갖고 있는데, 요소는 WeakReference를 확장하고 ThreadLocal 객체를 키로 사용하는 Entry 클래스를 의미합니다.

public class ThreadLocal<T> {
    // ...생략
	static class ThreadLocalMap {
		// ...생략
		static class Entry extends WeakReference<ThreadLocal<?>> {
			// ...생략
		}
	}
}

Thread


Thread 클래스는 ThreadLocalMap 타입의 멤버 필드를 가지고 있으며, 이는 특정 스레드의 정보를 ThreadLocal에서 직접 호출할 수 있도록 합니다.

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

ThreadLocal


ThreadLocal 클래스의 외부에 공개되는 public 메서드는 아래와 같습니다

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;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

getMap(Thread t) 메서드는 현재 스레드가 저장하고 있는 ThreadLocalMap 객체를 반환하게 됩니다.

createMap(Thread t, T firstValue) 메서드는 현재 스레드의 threadLocals 변수에 새로운 ThreadLocalMap 객체를 할당하며 현재의 ThreadLocal 객체와 Value 값을 key, value 쌍으로 집어넣습니다.

set(T value) 메서드는 현재 실행중인 스레드에 저장된 ThreadLocalMap 객체에다가 현재의 ThreadLocal 객체와 Value 값을 key, value 쌍으로 집어넣습니다.

get() 메서드는 현재 스레드를 확인한 후 getMap 메서드를 호출하여 특정 스레드의 ThreadLocalMap 객체를 가져와서 해당 객체의 Entry에 현재 ThreadLocal 객체를 key 값으로 가지는 value 값을 반환하게 됩니다.

스레드 풀(Thread Pool)을 사용할 때의 주의사항


위에서 언급했지만 스레드 로컬은 스레드 풀(thread pool)을 사용하는 환경에서 주의해야 합니다.

스레드가 재활용될 수 있기 때문에 사용이 끝났다면 스레드 로컬을 비워주는 과정이 필수적입니다.

사용이 끝난 스레드 로컬 정보는 제거될 수 있도록 remove 메서드를 스레드 반환전에 명시적으로 호출하면 됩니다.

public void run() {
    System.out.printf("%s Started,  ThreadLocal: %s%n", name, threadLocal.get());
    threadLocal.set(name);
    System.out.printf("%s Finished, ThreadLocal: %s%n", name, threadLocal.get());
    threadLocal.remove(); // `remove` 메서드를 호출한다.
}

📝 참고자료
자바 ThreadLocal: 사용법과 주의사항

profile
TO BE DEVELOPER

0개의 댓글