본문 바로가기

Java

ThreadLocal이란

사용자 별로 변수를 관리하고 싶은 경우가 있을 것이다.

 

아래와 같이 구현하면 컨트롤러는 빈 관리를 Singleton으로 하기 때문에 melon은 매번 재사용되며 요청때마다 증가하게 된다.

 

@RestController
public class ShoppingController {
 
  private int melon;
 
  @RequestMapping("/")
  private String test() {
    this.addMelon();
     
    return String.valueOf(melon);
  }
   
  private void addMelon() {
    melon++;
  }
}

 

그렇다면, 사용자별로 melon의 숫자가 증가하려면 어떻게 해야할까?

 

ThreadLocal 사용하기

아래와 같이 사용하면 Thread별로 저장소가 생성된다. 

public class ThreadRepository {
  private static ThreadLocal<Cart> threadLocal = ThreadLocal.withInitial(() -> new Cart());
   
  public static Cart getCart() {
    return threadLocal.get();
  }
   
  public static void remove() {
    threadLocal.remove();
  }
}

 

이제 요청시 생성되는 스레드에 변수가 할당되어 우리가 원했던 기능을 구현할 수 있다.

ThreadLocal 사용하면서 있었던 이슈

thread 별로 random 값을 만드는 부분인데,

 

local 환경에서는 잘되었지만, 테스트 환경 ( docker로 되어있는 microservices ) 에서는 되지 않았다. ThreadLocal.get() 부분에서 아무 응답이 없어서 결국은 504 gateway time-out이 났다.

private static final ThreadLocal<Serial> THREAD_LOCAL_SERIAL;

static {
	THREAD_LOCAL_SERIAL = ThreadLocal.withInitial(() -> new Serial(nextInt(), nextInt()));
}

public static long generate(byte shardId) {
  long time = MILLIS.between(EPOCH, Instant.now());
  Serial serial1 = THREAD_LOCAL_SERIAL.get();
  int serial = serial1.increment();
  return doGenerate(shardId, time, serial);
}

ThreadLocal.get() 코드를 좀 더 자세히 살펴보자.

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }