[JAVA] ThreadLocal 제대로 사용하기
ThreadLocal은 각 스레드가 독립적인 변수 복사본을 가질 수 있게 해주는 Java의 강력한 기능이다. 이를 통해 멀티스레드 환경에서 발생할 수 있는 동시성 문제를 해결할 수 있다.
ThreadLocal 기본 개념
ThreadLocal은 각 스레드마다 별도의 저장소를 제공하여 스레드 안전성을 보장한다.
public class UserContext {
private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
public static void clear() {
userHolder.remove(); // 중요: 메모리 누수 방지
}
}
동시성 문제 해결
일반적인 동시성 문제
// 동시성 문제가 있는 코드
public class UserService {
private User currentUser; // 여러 스레드가 공유하는 변수
public void setCurrentUser(User user) {
this.currentUser = user; // 동시성 문제 발생 가능
}
}
ThreadLocal을 사용한 해결
public class UserService {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public void setCurrentUser(User user) {
currentUser.set(user); // 스레드별로 독립된 저장소 사용
}
public void processUser() {
try {
User user = currentUser.get();
// 비즈니스 로직 처리
} finally {
currentUser.remove(); // 반드시 cleanup 수행
}
}
}
메모리 누수 방지
ThreadLocal 사용 시 주의해야 할 가장 중요한 점은 메모리 누수 방지다.
잘못된 사용 예
public class BadExample {
private static ThreadLocal<HeavyObject> threadLocal = new ThreadLocal<>();
public void process() {
threadLocal.set(new HeavyObject());
// 처리 로직
// remove() 호출 누락 -> 메모리 누수 발생
}
}
올바른 사용 예
public class GoodExample {
private static ThreadLocal<HeavyObject> threadLocal = new ThreadLocal<>();
public void process() {
try {
threadLocal.set(new HeavyObject());
// 처리 로직
} finally {
threadLocal.remove(); // 반드시 cleanup
}
}
}
Spring에서의 활용
Spring Framework에서는 ThreadLocal을 다양한 용도로 활용한다.
트랜잭션 컨텍스트 관리
@Service
public class TransactionService {
@Autowired
private TransactionTemplate transactionTemplate;
public void processInTransaction() {
transactionTemplate.execute(status -> {
// 트랜잭션 범위 내 작업
// Spring이 내부적으로 ThreadLocal 사용
return null;
});
}
}
사용자 인증 정보 관리
@Component
public class SecurityContextHolder {
private static final ThreadLocal<SecurityContext> contextHolder =
new ThreadLocal<>();
public static void setContext(SecurityContext context) {
contextHolder.set(context);
}
public static SecurityContext getContext() {
return contextHolder.get();
}
public static void clearContext() {
contextHolder.remove();
}
}
실무 사용 사례
1. MDC (Mapped Diagnostic Context)
public class RequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
MDC.put("requestId", generateRequestId());
MDC.put("userId", getCurrentUserId());
chain.doFilter(request, response);
} finally {
MDC.clear(); // ThreadLocal cleanup
}
}
}
2. 사용자 컨텍스트 관리
@Component
public class UserContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
try {
String token = extractToken(req);
User user = authenticateUser(token);
UserContext.setUser(user);
chain.doFilter(request, response);
} finally {
UserContext.clear();
}
}
}
성능과 메모리 고려사항
초기값 설정
ThreadLocal<User> userThreadLocal = ThreadLocal.withInitial(() -> new User("default"));
메모리 사용량 모니터링
public class ThreadLocalMonitor {
private static final Set<ThreadLocal<?>> threadLocals =
Collections.newSetFromMap(new WeakHashMap<>());
public static void register(ThreadLocal<?> threadLocal) {
threadLocals.add(threadLocal);
}
public static void printStats() {
System.out.println("Active ThreadLocals: " + threadLocals.size());
}
}
모범 사례 정리
- 항상 try-finally 블록 사용
- ThreadLocal 변수는 반드시 정리
- finally 블록에서 remove() 호출
- 스레드 풀 사용 시 주의
- 작업 완료 후 ThreadLocal 정리
- 스레드 재사용 시 이전 데이터 유출 방지
- 초기화 표준화
- withInitial() 메서드 활용
- null 체크 로직 간소화
'개발&프로그래밍' 카테고리의 다른 글
[Python] Pandas DataFrame (2) | 2024.11.19 |
---|---|
[Python] NumPy 배열 연산 마스터하기 (0) | 2024.11.18 |
[JAVA] 효과적인 예외 처리 전략 (0) | 2024.11.16 |
[JAVA] Java의 String Pool과 문자열 최적화 (2) | 2024.11.15 |
[JAVA] 가비지 컬렉션 동작 원리와 모니터링 방법 (1) | 2024.11.14 |
댓글