ThreadLocal

XingXi·2024년 6월 20일
0

기록

목록 보기
28/33

Thread Local

Java Thread 에서 Thread 마다 독자적으로 사용할 수 있는 로컬 변수 저장소를 말한다. Thread 간 공유되지 않는 독자적인 메모리 공간이며 때문에 Thread 마다
다른 값을 할당할 때 사용하면 좋다.

  • Auth
  • Transaction
  • Log 추적 등 ..
    Thread 마다 다른 값을 할당하기에 멀티 스레드 환경에서 동시성을 보장 받을 수 있다.

Thread Local 은 처음 부터 존재하는가?

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 과 같이 사용할 때는 주의해야한다.

ThreadLocal 에 값을 사용하고 데이터를 삭제하지 않아도, 일시적인 메모리를 점유하는 것 말고는 딱히 문제가 되지 않는다.

하지만 Thread Pool 을 사용하고, Thread Local 에 할당한 값을 제거하지 않으면
다른 사용자가 값을 사용할때, 이전에 사용한 값이 할당 될 수 있다.

SpringBoot

AsyncConfig → App 의 ThreadPool 개수 제한

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

ThreadLocalService → ThreadLocal 을 할당하는 Class

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

MyService → Async 를 사용하여 비동기로 method 를 실행

@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");
    }
}

ThreadLocalTest → Test 클래스

@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 에 쓴 값이 남게 된다. 민감한 정보의 경우 큰 문제가 발생할 수 있다.

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(); // ** 추가 
    }

값이 전부 지워진 것을 볼 수 있다.

ThreadLocal 이 상속이 된다

부모 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 파일을 보면

말 그대로 자식 스레드가 부모 스레드의 스레드 로컬 값을 상속 받기 때문에 받아서
사용할 수 있지만 부모 스레드 로컬 값에는 직접적으로 영향을 주진 않는다.

0개의 댓글