Java Thread 에서 Thread 마다 독자적으로 사용할 수 있는 로컬 변수 저장소를 말한다. Thread 간 공유되지 않는 독자적인 메모리 공간이며 때문에 Thread 마다
다른 값을 할당할 때 사용하면 좋다.
- Auth
- Transaction
- Log 추적 등 ..
Thread 마다 다른 값을 할당하기에 멀티 스레드 환경에서 동시성을 보장 받을 수 있다.
Thread 내부에 threadLocals 속성에 ThreadLocalMap 객체를 할당한다.
ThreadLocals 의 기본값은 null 이며 ThreadLocal 을 사용할 때 생성된다.
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal 에 값을 사용하고 데이터를 삭제하지 않아도, 일시적인 메모리를 점유하는 것 말고는 딱히 문제가 되지 않는다.
하지만 Thread Pool 을 사용하고, Thread Local 에 할당한 값을 제거하지 않으면
다른 사용자가 값을 사용할때, 이전에 사용한 값이 할당 될 수 있다.
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor takExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3); // 기본 스레드 개수
executor.setMaxPoolSize(3); // 최대 스레드 개수
executor.setQueueCapacity(2); // 큐 용량
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
}
import org.springframework.stereotype.Service;
@Service
public class ThreadLocalService {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void setThreadLocal(String value) {
threadLocal.set(value);
}
public String getThreadLocal() {
String value = threadLocal.get();
return value;
}
public void clearThreadLocal() {
threadLocal.remove();
}
}
@Service
@EnableAsync
@RequiredArgsConstructor
public class MyService {
private final ThreadLocalService service;
public static StringBuilder beforeValue = new StringBuilder();
public static StringBuilder afterValue = new StringBuilder();
@Async("taskExecutor")
public void asyncMethod(String value) {
String beforeRetrievedValue = service.getThreadLocal();
beforeValue.append("Before Retrived Value : "+beforeRetrievedValue+" in thread: "+Thread.currentThread().getName()+"\n");
service.setThreadLocal(value);
String retrievedValue = service.getThreadLocal();
afterValue.append("Value in asyncMethod: " + retrievedValue + " in thread: " + Thread.currentThread().getName()+"\n");
}
}
@SpringBootTest
public class ThreadLocalTest {
@Autowired
private MyService mService;
@Test
void 테스트() throws InterruptedException{
System.out.println(":: Thread Local Test START ::");
System.out.println("");
System.out.println("");
for (int i = 1; i < 6; i++) {
mService.asyncMethod("Value-" + i);
}
Thread.sleep(3000);
System.out.println("before values : ");
System.out.println(mService.beforeValue);
System.out.println();
System.out.println("after values : ");
System.out.println(mService.afterValue);
System.out.println("");
System.out.println("");
System.out.println(":: Thread Local Test END ::");
}
}
이전에 ThreadLocal 에 쓴 값이 남게 된다. 민감한 정보의 경우 큰 문제가 발생할 수 있다.
@Async("taskExecutor")
public void asyncMethod(String value) {
String beforeRetrievedValue = service.getThreadLocal();
System.out.println("Before Retrived Value : "+beforeRetrievedValue+" in thread: "+Thread.currentThread().getName());
service.setThreadLocal(value);
String retrievedValue = service.getThreadLocal();
System.out.println("Value in asyncMethod: " + retrievedValue + " in thread: " + Thread.currentThread().getName());
service.clearThreadLocal(); // ** 추가
}
값이 전부 지워진 것을 볼 수 있다.
부모 Thread 의 ThreadLocal 값을 사용할 니즈가 있을때 좋은 API 이다.
신기한 점은 자식 Thread 에서 부모 Thread 의 ThreadLocal 을 읽기만 가능하고
편집을 한다고 해도 적용되지 않는 것을 볼 수 있다..
public class MySelf_Inheritance {
public static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
inheritableThreadLocal.set("Parent Val");
Thread child = new Thread(()->{
System.out.println("ThreadLocal Value from Parent : "+inheritableThreadLocal.get());
inheritableThreadLocal.set("Child Val");
System.out.println("Update ThreadLocal Value from child : "+inheritableThreadLocal.get());
});
child.start();
Thread.sleep(1000);
System.out.println("THreadLocal Value of Parent : "+inheritableThreadLocal.get());
}
}
자식 Thread 에서 부모 ThreadLocal 의 값을 변경하고 확인을 했음에도 정작
부모 Thread 에서 확인할때는 적용되지 않은 것을 볼 수 있다.
ThreadLocal 에 대한 class 파일을 보면
말 그대로 자식 스레드가 부모 스레드의 스레드 로컬 값을 상속 받기 때문에 받아서
사용할 수 있지만 부모 스레드 로컬 값에는 직접적으로 영향을 주진 않는다.