본문 바로가기

JAVA

[JAVA] Thread Local이란 무엇인가? 기본 개념부터 활용까지!

728x90
반응형

Thread Local이란 무엇인가?

ThreadLocal은 자바에서 멀티스레딩 프로그래밍을 할 때 사용되는 클래스입니다. 각 스레드가 독립적으로 값을 가지도록 해주며, 여러 스레드가 동시에 같은 변수를 사용할 때 발생할 수 있는 문제를 피할 수 있습니다.

일반적으로 멀티스레딩 환경에서 공유 변수는 동기화가 필요합니다. 하지만 동기화는 성능에 영향을 미칠 수 있습니다. ThreadLocal을 사용하면 스레드마다 고유한 변수를 가질 수 있기 때문에 동기화가 필요 없습니다.

ThreadLocal의 주요 메서드는 다음과 같습니다:

  • get(): 현재 스레드의 값을 반환합니다.
  • set(T value): 현재 스레드의 값을 설정합니다.
  • remove(): 현재 스레드의 값을 삭제합니다.
public class ThreadLocalExample {
    // ThreadLocal 변수를 생성합니다.
    private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        // 첫 번째 스레드
        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 initial value: " + threadLocalValue.get());
            threadLocalValue.set(100);
            System.out.println("Thread 1 updated value: " + threadLocalValue.get());
        });

        // 두 번째 스레드
        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 initial value: " + threadLocalValue.get());
            threadLocalValue.set(200);
            System.out.println("Thread 2 updated value: " + threadLocalValue.get());
        });

        // 스레드를 시작합니다.
        thread1.start();
        thread2.start();

        // 메인 스레드에서 값을 확인합니다.
        System.out.println("Main thread value: " + threadLocalValue.get());
    }
}
  1. ThreadLocal 변수를 threadLocalValue로 선언하고, 초기값을 1로 설정했습니다.
  2. 두 개의 스레드를 생성하여 각 스레드에서 threadLocalValue의 값을 출력하고 변경합니다.
  3. 각 스레드는 자신만의 ThreadLocal 값을 가지므로 서로 영향을 주지 않습니다.

위 내용의 예상 결과 입니다.

Main thread value: 1
Thread 1 initial value: 1
Thread 2 initial value: 1
Thread 1 updated value: 100
Thread 2 updated value: 200

이 예제에서 보시다시피, 각 스레드는 ThreadLocal 변수를 통해 독립적인 값을 가집니다. 메인 스레드, thread1, thread2 모두 자신만의 값을 유지하고 있습니다.

이처럼 ThreadLocal을 사용하면 멀티스레딩 환경에서 동기화 없이도 안전하게 각 스레드가 고유한 상태를 유지할 수 있습니다.

Thread Local의 장점

구분
동기화 불필요 ThreadLocal을 사용하면 각 스레드가 독립적인 값을 가지기 때문에 동기화(synchronization)가 필요 없습니다. 이는 성능을 향상시키는 데 도움이 됩니다.
코드 간결성 복잡한 동기화 코드 없이도 각 스레드마다 고유한 데이터를 저장하고 접근할 수 있어 코드가 간결해집니다.
상태 유지 각 스레드가 독립적으로 상태를 유지할 수 있습니다. 예를 들어, 데이터베이스 연결이나 세션 정보를 스레드별로 저장할 때 유용합니다.
재사용 가능 재사용이 가능한 객체(예: 포맷터, 데이터베이스 연결 등)를 스레드별로 할당하여 여러 스레드에서 안전하게 사용할 수 있습니다.

Thread Local 주의할 

메모리 누수

ThreadLocal은 강한 참조(strong reference)를 사용하므로, 스레드가 종료된 후에도 ThreadLocal 변수가 참조하는 객체가 해제되지 않으면 메모리 누수가 발생할 수 있습니다. 이를 방지하기 위해 remove() 메서드를 사용하여 스레드가 끝날 때 ThreadLocal 값을 명시적으로 제거해야 합니다.

threadLocalValue.remove();

스레드 풀 사용 시 주의

스레드 풀에서 스레드를 재사용할 때, 이전 작업의 ThreadLocal 값이 다음 작업에 영향을 줄 수 있습니다. 따라서, 작업이 끝날 때마다 ThreadLocal 값을 정리해 주어야 합니다.

복잡성 증가

ThreadLocal을 과도하게 사용하면 코드의 복잡성이 증가할 수 있습니다. 유지보수 및 디버깅이 어려워질 수 있으므로, 꼭 필요한 경우에만 사용해야 합니다.

예기치 않은 동작

ThreadLocal은 각 스레드마다 독립된 값을 가지므로, 값이 예기치 않게 변하지 않는다는 보장이 없습니다. 값을 읽고 쓰는 순서를 명확히 해야 합니다.

다음은 ThreadLocal 사용 후 값을 정리하는 예제입니다

public class ThreadLocalCleanupExample {
    private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " initial value: " + threadLocalValue.get());
                threadLocalValue.set((int) (Math.random() * 100));
                System.out.println(Thread.currentThread().getName() + " updated value: " + threadLocalValue.get());
            } finally {
                // 작업이 끝난 후 ThreadLocal 값을 정리합니다.
                threadLocalValue.remove();
            }
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

이 예제에서는 finally 블록에서 threadLocalValue.remove()를 호출하여 스레드가 끝난 후 ThreadLocal 값을 정리합니다. 이렇게 하면 메모리 누수 문제를 방지할 수 있습니다.

728x90
반응형