ThreadLocal

kims·2023년 12월 13일
0

자바

목록 보기
4/5
post-thumbnail

ThreadLocal이란?

  • java.lang 패키지에 속한 클래스로 Thread별 독립적으로 초기화된 변수를 제공한다.
  • ThreadLocal 내부는 Thread의 정보를 Key 값으로 하여 값을 저장하는 Map 구조(ThreadLocalMap)를 가지고 있다.

주요 메서드

1. get()

  • 현재 Thread의 ThreadLocal 변수 값 반환

2. set()

  • 현재 Thread의 ThreadLocal 변수 값 설정

3. remove()

  • 현재 Thread의 ThreadLocal 변수 값 제거

4. withInitial()

  • 현재 Thread의 ThreadLocal 변수 생성 및 특정 값으로 초기화

예제 코드 (1) 동시성 문제 발생

public class MyRunnable implements Runnable {
    
    private static int var = 0;
    private int order;

    public MyRunnable(int order) {
        this.order = order;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            var += 1;

            String str = i == 0 ? " --init" : "";
            System.out.println("Thread - " + order + " / " + " [" + i + "] " + " - " + var + str);
        }
    }

}


public class ThreadLocalBefore {

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(new MyRunnable(i));
            thread.start();
        }
    }

}

💡 동시성 문제

  • 다수의 Thread가 하나의 공유 데이터(동일한 자원)를 조작할 때 발생하는 문제로 데이터 정합성 문제가 발생한다.

예제 코드 (2) 동시성 문제 해결 - ThreadLocal 사용

public class ThreadLocalMyRunnable implements Runnable {
    
    private static final ThreadLocal<Integer> var = ThreadLocal.withInitial(() -> 0);
    private boolean removalStatus;
    private int order;
    
    public ThreadLocalMyRunnable(boolean removalStatus, int order) {
        this.removalStatus = removalStatus;
        this.order = order;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            var.set(var.get() + 1);

            String str = i == 0 ? " --init" : "";
            System.out.println("Thread - " + order + " / " + " [" + i + "] " + " - " + var.get() + str);
        }

        if (removalStatus) {
            var.remove();
        }
    }
}


public class ThreadLocalAfter {

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(new ThreadLocalMyRunnable(false, i));
            thread.start();
        }
    }

}

remove()메서드 중요성

  • 작업이 끝난 후 ThreadLocal 값을 제거하지 않으면, 추후 다시 ThreadLocal이 사용될 때 이전 값을 재사용하게 된다. 이를 방지하기 위해 remove()메서드를 호출해 값을 지워주는 것이 좋다.

예제 코드 (1) remove()적용 전

newFixedThreadPool() : 고정된 크기의 스레드 풀 생성

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

public class ThreadLocalRemoveBefore {
    
    private static final ExecutorService executorService = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new ThreadLocalMyRunnable(false, i));
            executorService.execute(thread);
        }

        executorService.shutdown();
    }

}

💡 Thread Pool

  • 스레드 풀은 작업처리에 사용되는 Thread를 제한된 개수만큼 정해놓고 작업큐 (Queue)에 들어오는 작업들을 하나씩 Thread가 맡아 처리하는 것
  • 생성된 Thread는 작업을 마치면 즉시 종료되지 않고, 대신 풀에 반환되어 다음 작업을 수행할 수 있도록 재사용된다. 즉, 종료된 Thread는 풀로 반환되기에 바로 GC의 대상이 되지 않아 GC 동작에 따른 오버헤드1가 감소된다.
  • 여러 번 Runtime Data Area에 Thread를 생성하지 않고, 특정 수의 Thread를 미리 생성해 재사용함으로써 Thread의 생성 및 소멸에 따른 오버헤드를 최소화하고, 성능을 향상시킨다.
  • Thread Pool을 사용하지 않고 필요 이상으로 Thread를 생성하면 메모리와 CPU 잠식2 문제가 발생할 수 있다.
1: 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간 · 메모리 등을 말한다. 출처👉위키백과
2: 빈번한 컨텍스트 스위칭 (Context Switching, 기존 프로세스의 Context를 저장하고, 다음 프로세스를 실행할 수 있도록 Context를 교체하는 작업)이 발생하여 CPU 자원을 사용하고 잠식하는 현상이 초래한다.

예제 코드 (2) remove()적용 후

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

public class ThreadLocalRemoveAfter {
    
    private static final ExecutorService executorService = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new ThreadLocalMyRunnable(true, i));
            executorService.execute(thread);
        }

        executorService.shutdown();
    }

}


💡참고

profile
기술로 세상을 이롭게

0개의 댓글