ThreadLocal 사용법과 활용
출처 : http://javacan.tistory.com/153
자바 1.2 버전부터 제공되고 있지만 아직 다수의 개발자들이 잘 몰라서 활용을 잘 못하는 기능이 하나 있는데, 그 기능이 바로
쓰레드 단위로 로컬 변수를 할당하는 기능이다. 이 기능은 ThreadLocal 클래스를 통해서 제공되는데, 본 글에서는
ThreadLocal 클래스의 기본적인 사용방법과 활용 방법을 살펴보도록 하겠다.
ThreadLocal이란?
일반 변수의 수명은 특정 코드 블록(예, 메서드 범위, for 블록 범위 등) 범위 내에서만 유효하다.
{
int a = 10;
...
// 블록 내에서 a 변수 사용 가능
}
// 변수 a는 위 코드 블록이 끝나면 더 이상 유효하지 않다. (즉, 수명을 다한다.)
int a = 10;
...
// 블록 내에서 a 변수 사용 가능
}
// 변수 a는 위 코드 블록이 끝나면 더 이상 유효하지 않다. (즉, 수명을 다한다.)
반면에 ThreadLocal을 이용하면 쓰레드 영역에 변수를 설정할 수 있기 때문에, 특정 쓰레드가 실행하는 모든 코드에서 그 쓰레드에 설정된 변수 값을 사용할 수 있게 된다. 아래 그림은 쓰레드 로컬 변수가 어떻게 동작하는 지를 간단하게 보여주고 있다.
위 그림에서 주목할 점은 동일한 코드를 실행하는 데, 쓰레드1에서 실행할 경우 관련 값이 쓰레드1에 저장되고 쓰레드2에서 실행할 경우 쓰레드2에 저장된다는 점이다.
ThreadLocal의 기본 사용법
ThreadLocal의 사용방법은 너무 쉽다. 단지 다음의 네 가지만 해 주면 된다.
- ThreadLocal 객체를 생성한다.
- ThreadLocal.set() 메서드를 이용해서 현재 쓰레드의 로컬 변수에 값을 저장한다.
- ThreadLocal.get() 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 읽어온다.
- ThreadLocal.remove() 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 삭제한다.
// 현재 쓰레드와 관련된 로컬 변수를 하나 생성한다.
ThreadLocal<UserInfo> local = new ThreadLocal<UserInfo>();
// 로컬 변수에 값 할당
local.set(currentUser);
// 이후 실행되는 코드는 쓰레드 로컬 변수 값을 사용
UserInfo userInfo = local.get();
ThreadLocal<UserInfo> local = new ThreadLocal<UserInfo>();
// 로컬 변수에 값 할당
local.set(currentUser);
// 이후 실행되는 코드는 쓰레드 로컬 변수 값을 사용
UserInfo userInfo = local.get();
위 코드만으로는 ThreadLocal이 어떻게 동작하는 지 잘 이해가 되지 않을테니, 구체적인 예제를 이용해서
ThreadLocal의 동작 방식을 살펴보도록 하겠다. 먼저 ThreadLocal 타입의 static 필드를 갖는 클래스를 하나
작성해보자.
public class Context {
public static ThreadLocal<Date> local = new ThreadLocal<Date>();
}
public static ThreadLocal<Date> local = new ThreadLocal<Date>();
}
이제 Context 클래스를 사용해서 쓰레드 로컬 변수를 설정하고 사용하는 코드를 작성할 차례이다. 아래는 코드의 예이다.
class A {
public void a() {
Context.local.set(new Date());
B b = new B();
b.b();
Context.local.remove();
}
}
class B {
public void b() {
Date date = Context.local.get();
C c = new C();
c.c();
}
}
class C {
public void c() {
Date date = Context.local.get();
}
}
public void a() {
Context.local.set(new Date());
B b = new B();
b.b();
Context.local.remove();
}
}
class B {
public void b() {
Date date = Context.local.get();
C c = new C();
c.c();
}
}
class C {
public void c() {
Date date = Context.local.get();
}
}
위 코드를 보면 A, B, C 세 개의 클래스가 존재하는데, A.a() 메서드를 호출하면 다음 그림과 같은 순서로 메서드가 실행된다.
위 그림에서 1~10은 모두 하나의 쓰레드에서 실행된다. ThreadLocal과 관련된 부분을 정리하면 다음과 같다.
- 2 - A.a() 메서드에서 현재 쓰레드의 로컬 변수에 Date 객체를 저장한다.
- 4 - B.b() 메서드에서 현재 쓰레드의 로컬 변수에 저장된 Date 객체를 읽어와 사용한다.
- 6 - C.c() 메서드에서 현재 쓰레드의 로컬 변수에 저장된 Date 객체를 읽어와 사용한다.
- 9 - A.a() 메서드에서 현재 쓰레드의 로컬 변수를 삭제한다.
ThreadLocal의 활용
ThreadLocal은 한 쓰레드에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해 주기 때문에 쓰레드와 관련된 코드에서 파라미터를 사용하지 않고 객체를 전파하기 위한 용도로 주로 사용되며, 주요 용도는 다음과 같다.
- 사용자 인증정보 전파 - Spring Security에서는 ThreadLocal을 이용해서 사용자 인증 정보를 전파한다.
- 트랜잭션 컨텍스트 전파 - 트랜잭션 매니저는 트랜잭션 컨텍스트를 전파하는 데 ThreadLocal을 사용한다.
- 쓰레드에 안전해야 하는 데이터 보관
ThreadLocal 사용시 주의 사항
쓰레드 풀 환경에서 ThreadLocal을 사용하는 경우 ThreadLocal 변수에 보관된 데이터의 사용이 끝나면 반드시 해당 데이터를 삭제해 주어야 한다. 그렇지 않을 경우 재사용되는 쓰레드가 올바르지 않은 데이터를 참조할 수 있다.