JAVA

[Java] Thread Local

lakelight 2022. 10. 24. 14:02
728x90
반응형

ThreadLocal

스레드 단위로 로컬 변수를 사용할 수 있다는 장점이 있습니다. 또한 여러 메서드를 전역변수처럼 사용할 수 있습니다. 다만 다른 스레드와 변수가 공유되지 않도록 주의해야 합니다.

 

ThreadLocalMap

private 클래스로 구성되어 있어 외부에서 접근할 수 없고, 내부적으로 해시 테이블 정보를 갖고 있는데, 요소는 WeakReference를 확장하고 ThreadLocal 객체를 키로 사용하는 Entry 클래스입니다.

 

Thread

ThreadLocalMap를 타입 멤버 필드로 가지고 있는데, 특정 스레드의 정보를 TreadLocal에서 직접 호출 할 수 있도록 합니다.

 

ThreadLocal의 withInital 메서드

쓰레드 로컬 변수를 생성하면서 특정 값으로 초기화하는 메서드입니다.

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

 

ThreadLocal의 set, get 메서드

set은 ThreadLocal에 값을 저장하고, get은 값을 가져오는 메서드입니다.

public void set(T value) {
    Thread t = Thread.currentThread(); // 현재 쓰레드를 가져와서,
    ThreadLocalMap map = getMap(t); // 쓰레드 로컬 맵 가져와서,
    if (map != null) { // 만약 맵이 널이 아니라면 쓰레드 로컬에 값 저장
        map.set(this, value);
    } else {
        createMap(t, value); // 맵이 널이라면 맵 생성 후 쓰레드 로컬에 값 저장
    }
}

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(); // 맵이 널이라면 값 초기화
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; // 쓰레드의 쓰레드 로컬 맵 반환
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue); // 쓰레드 로컬 맵 생성 & 초기화
}

 

ThreadLocal의 remove 메서드

쓰레드 로컬 변수 값을 삭제하는 메서드입니다. 쓰레드 풀을 사용하는 과정에서 쓰레드 로컬 변수 사용이 끝났다면 remove를 통해 쓰레드 변수 값을 삭제해야 합니다. 왜냐하면 쓰레드가 재활용되면 이전에 설정했던 쓰레드 로컬 정보가 남아있을 수 있기 때문입니다.

public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

 

ThreadLocal 예제

public class ThreadLocalTest {

    // 스레드 클래스
    static class MadThread extends Thread {
        // 쓰레드 로컬의 이름을 초기화 해줍니다. (기본값 설정)
        private static final ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Thread Init");
        private final String name;

        public MadThread(String name) {
            this.name = name;
        }


        @Override
        public void run() {
            // 1) 이 부분에서는 runTest()에서 생성자를 통해 저장한 name 변수가 설정되지 않아서 withInitial 에서 저장된 Thread Init이 반환됩니다.
            System.out.printf("%s Started,  ThreadLocal: %s%n", name, threadLocal.get());

            // 2) 이 부분에서는 name에 저장된 데이터를 treadLocal에 설정해줍니다.
            threadLocal.set(name);

            // 3) 위에 set을 통해 ThreadLocal의 값을 설정해주었기 때문에 name이 설정된 name으로 반환됩니다.
            System.out.printf("%s Finished, ThreadLocal: %s%n", name, threadLocal.get());
        }
    }

    public void runTest() {
        for (int threadCount = 1; threadCount <= 5; threadCount++) {
            // 쓰레드의 이름을 설정해줍니다.
            final MadThread thread = new MadThread("thread-" + threadCount);
            thread.start();
        }
    }

    public static void main(String[] args) {
        new ThreadLocalTest().runTest();
    }
}

결과

쓰레드 간 간섭 없이 데이터가 잘 출력됩니다.

> Task :ThreadLocalTest.main()
thread-1 Started, ThreadLocal: Thread Init
thread-4 Started, ThreadLocal: Thread Init
thread-3 Started, ThreadLocal: Thread Init
thread-3 Finished, ThreadLocal: thread-3
thread-5 Started, ThreadLocal: Thread Init
thread-5 Finished, ThreadLocal: thread-5
thread-2 Started, ThreadLocal: Thread Init
thread-2 Finished, ThreadLocal: thread-2
thread-4 Finished, ThreadLocal: thread-4
thread-1 Finished, ThreadLocal: thread-1

 

쓰레드 풀을 사용할 때 주의할 점

쓰레드 풀은 쓰레드를 재사용하기 때문에 값이 초기화 되어 있지 않을 수 있습니다.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadLocalTest {

    static class MadThread extends Thread {
        private static final ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Thread Init");
        private final String name;

        public MadThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.printf("%s Started,  ThreadLocal: %s%n", name, threadLocal.get());
            threadLocal.set(name);
            System.out.printf("%s Finished, ThreadLocal: %s%n", name, threadLocal.get());
        }
    }

    // 스레드 풀 선언
    private final ExecutorService executorService = Executors.newFixedThreadPool(3);

    public void runTest() {
        for (int threadCount = 1; threadCount <= 5; threadCount++) {
            final String name = "thread-" + threadCount;
            final MadThread thread = new MadThread(name);
            executorService.execute(thread);
        }

        // 스레드 풀 종료
        executorService.shutdown();

        // 스레드 풀 종료 대기
        while (true) {
            try {
                if (executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                    break;
                }
            } catch (InterruptedException e) {
                System.err.println("Error: " + e);
                executorService.shutdownNow();
            }
        }
        System.out.println("All threads are finished");
    }

    public static void main(String[] args) {
        new ThreadLocalTest().runTest();
    }
}

결과

> Task :ThreadLocalTest.main()
thread-1 Started, ThreadLocal: Thread Init
thread-1 Finished, ThreadLocal: thread-1
thread-4 Started, ThreadLocal: thread-1
thread-4 Finished, ThreadLocal: thread-4
thread-5 Started, ThreadLocal: thread-4
thread-5 Finished, ThreadLocal: thread-5
thread-2 Started, ThreadLocal: Thread Init
thread-2 Finished, ThreadLocal: thread-2
thread-3 Started, ThreadLocal: Thread Init
thread-3 Finished, ThreadLocal: thread-3
All threads are finished

 

쓰레드 풀을 사용할 때는 remove 사용

public void run() {
    System.out.printf("%s Started,  ThreadLocal: %s%n", name, threadLocal.get());
    threadLocal.set(name);
    System.out.printf("%s Finished, ThreadLocal: %s%n", name, threadLocal.get());
    threadLocal.remove(); // `remove` 메서드를 호출한다.
}

결과

> Task :ThreadLocalTest.main()
thread-1 Started, ThreadLocal: Thread Init
thread-1 Finished, ThreadLocal: thread-1
thread-4 Started, ThreadLocal: Thread Init
thread-4 Finished, ThreadLocal: thread-4
thread-5 Started, ThreadLocal: Thread Init
thread-3 Started, ThreadLocal: Thread Init
thread-3 Finished, ThreadLocal: thread-3
thread-2 Started, ThreadLocal: Thread Init
thread-2 Finished, ThreadLocal: thread-2
thread-5 Finished, ThreadLocal: thread-5
All threads are finished

 

[참고]

1. 자바 ThreadLocal: 사용법과 주의사항

 

 

728x90
반응형

'JAVA' 카테고리의 다른 글

[Java] Mutable & Immutable  (2) 2022.11.23
[Java] 자바 메모리 영역 관련 노트 정리  (0) 2022.10.24
[Java] Collection - Map  (0) 2022.09.19
[Java] Collection - Set  (0) 2022.09.19
[Java] Collection - List  (0) 2022.09.19