ThreadLocal이란
ThreadLocal은 JDK 1.2부터 제공된 오래된 클래스입니다.
ThreadLocal 클래스를 활용하면 스레드 단위로 로컬 변수를 사용할 수 있기 때문에 마치 전역 변수처럼 여러 메서드에서 활용할 수 있습니다.
ThreadLocal 클래스는 thread-local 변수들을 제공합니다.
이 변수들은 각 스레드가 독립적으로 변수의 초기화 된 사본을 가지고 있습니다.
ThreadLocal 인스턴스들은 보통 스레드와 상태를 연결하려고 하는 클래스들의 private static 필드들입니다.
(예를 들어, 유저 ID 또는 트랜잭션 ID)
제공되는 메서드
set, get, remove - threadLocal의 로컬 변수 조작
withInitial - static 메서드를 이용한 객체 생성
활용 방안( 예시 )
객체 공유 방안 ( 파라미터, 전역 변수..)
ThreadLocal은 하나의 Thread에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해 주기 때문에
Thread와 관련된 코드에서 파라미터를 사용하지 않고 객체를 전파하기 위한 용도로 주로 사용됩니다.
주요 용도는 다음과 같습니다.
사용자 인증정보 전파 - Spring Security에서는 ThreadLocal을 이용해서 사용자 인증 정보를 전파합니다.
트랜잭션 컨텍스트 전파 - TransactionManager는 TransactionContext를 전파하는 데 ThreadLocal을 사용합니다.
스레드에 안전해야 하는 데이터 보관
이 외에도 Thread 기준으로 동작해야 하는 기능을 구현할 때 ThreadLocal을 유용하게 사용할 수 있습니다.
SpringSecurity의 SecurityContextHolder
DispatcherServlet을 통해 요청이 들어오면, 요청을 받은 Thread별 인증 정보가 ThreadLocal에 보관되고,
Thread 내에서 계속 공유가 된다.
사용 시 주의사항
Thread를 생성하는 비용이 비싸기 때문에, Thread를 미리 만들어 pool을 생성해 재활용합니다.
ThreadLocal을 사용 시, 다 사용된 값은 비워주어야 다음에 Thread 동작에 오동작을 방지할 수 있습니다.
public class ThreadLocalTest {
// private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "str");
private String threadLocal_str = "";
public void test_thread_local(String str) {
threadLocal.set(str);
ThreadLocalTest.sleep(1000);
System.out.println("ThreadLocal Test : " + threadLocal.get());
threadLocal.remove();
}
public void test(String str) {
threadLocal_str = str;
ThreadLocalTest.sleep(1000);
System.out.println("ThreadLocal-Normal Test : " + threadLocal_str);
}
public static void main(String[] args) {
// normalTest();
threadLocalTest();
}
private static void threadLocalTest() {
ThreadLocalTest localTest = new ThreadLocalTest();
Runnable one = () -> localTest.test_thread_local("test1");
Runnable two = () -> localTest.test_thread_local("test2");
Thread thread1 = new Thread(one);
thread1.setName("thread1");
Thread thread2 = new Thread(two);
thread2.setName("thread2");
thread1.start();
ThreadLocalTest.sleep(100);
thread2.start();
ThreadLocalTest.sleep(3000);
}
private static void normalTest() {
ThreadLocalTest localTest = new ThreadLocalTest();
Runnable one = () -> localTest.test("test1");
Runnable two = () -> localTest.test("test2");
Thread thread1 = new Thread(one);
thread1.setName("thread1");
Thread thread2 = new Thread(two);
thread2.setName("thread2");
thread1.start();
ThreadLocalTest.sleep(100);
thread2.start();
ThreadLocalTest.sleep(3000);
}
private static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}