이 단원에서는 java.lang.ThreadLocal<> 클래스 작업과 다중 스레드 환경에서 이를 사용하는 방법 에 대해 일반적으로 설명합니다 .

ThreadLocal 클래스는 변수를 저장하는 사용됩니다. 이 클래스의 특징은 이를 사용하는 각 스레드에 대한 값의 별도의 독립적인 복사본을 유지한다는 것입니다.

클래스의 작동을 더 깊이 파고들면 스레드를 값에 매핑하는 맵을 상상할 수 있습니다. 현재 스레드는 이를 사용해야 할 때 적절한 값을 가져옵니다.

ThreadLocal 클래스 생성자

건설자 행동
스레드로컬() Java에서 빈 변수를 생성합니다.

행동 양식

방법 행동
얻다() 현재 스레드의 지역 변수 값을 반환합니다.
세트() 현재 스레드에 대한 지역 변수의 값을 설정합니다.
제거하다() 현재 스레드의 지역 변수 값을 제거합니다.
ThreadLocal.withInitial() 초기값을 설정하는 추가적인 팩토리 메소드

get() 및 설정()

두 개의 카운터를 만드는 예제를 작성해 보겠습니다. 첫 번째 일반 변수는 스레드 수를 계산하기 위한 것입니다. 두 번째는 ThreadLocal 로 래핑합니다 . 그리고 우리는 그들이 어떻게 함께 작동하는지 볼 것입니다. 먼저 Runnable을 상속 하고 데이터와 가장 중요한 run() 메서드를 포함하는 ThreadDemo 클래스를 작성해 보겠습니다 . 또한 화면에 카운터를 표시하는 방법도 추가합니다.


class ThreadDemo implements Runnable {

    int counter;
    ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>();

    public void run() {
        counter++;

        if(threadLocalCounter.get() != null) {
            threadLocalCounter.set(threadLocalCounter.get() + 1);
        } else {
            threadLocalCounter.set(0);
        }
        printCounters();
    }

    public void printCounters(){
        System.out.println("Counter: " + counter);
        System.out.println("threadLocalCounter: " + threadLocalCounter.get());
    }
}

수업이 진행될 때마다 우리는카운터변수는 get() 메서드를 호출하여 ThreadLocal 변수 에서 데이터를 가져옵니다 . 새 스레드에 데이터가 없으면 0으로 설정합니다. 데이터가 있으면 1씩 늘립니다. 그리고 우리의 주요 방법을 작성해 봅시다 .


public static void main(String[] args) {
    ThreadDemo threadDemo = new ThreadDemo();

    Thread t1 = new Thread(threadDemo);
    Thread t2 = new Thread(threadDemo);
    Thread t3 = new Thread(threadDemo);

    t1.start();
    t2.start();
    t3.start();

}

클래스를 실행하면 ThreadLocal 변수는 액세스하는 스레드에 관계없이 동일하게 유지되지만 스레드 수는 증가합니다.

카운터: 1
카운터: 2
카운터: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0

프로세스가 종료 코드 0으로 종료됨

제거하다()

remove 메서드의 작동 방식을 이해하기 위해 ThreadDemo 클래스 의 코드를 약간 변경합니다 .


if(threadLocalCounter.get() != null) {
      threadLocalCounter.set(threadLocalCounter.get() + 1);
  } else {
      if (counter % 2 == 0) {
          threadLocalCounter.remove();
      } else {
          threadLocalCounter.set(0);
      }
  }

이 코드에서 스레드 카운터가 짝수이면 ThreadLocal 변수 에서 remove() 메서드를 호출합니다 . 결과:

카운터: 3
threadLocalCounter: 0
카운터: 2
threadLocalCounter: null
카운터: 1
threadLocalCounter: 0

프로세스가 종료 코드 0으로 종료됨

여기서 우리는 두 번째 스레드의 ThreadLocal 변수가 null 임을 쉽게 알 수 있습니다 .

ThreadLocal.withInitial()

이 메서드는 스레드 로컬 변수를 만듭니다.

ThreadDemo 클래스 구현 :


class ThreadDemo implements Runnable {

    int counter;
    ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 1);

    public void run() {
        counter++;
        printCounters();
    }

    public void printCounters(){
        System.out.println("Counter: " + counter);
        System.out.println("threadLocalCounter: " + threadLocalCounter.get());
    }
}

그리고 코드의 결과를 볼 수 있습니다.

카운터: 1
카운터: 2
카운터: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1

프로세스가 종료 코드 0으로 종료됨

왜 그런 변수를 사용해야 합니까?

ThreadLocal은 java.lang.Thread 실행 스레드와 관련하여 로컬 변수에 대한 추상화를 제공합니다.

ThreadLocal 변수는 각 스레드가 get() set() 메서드 를 통해 액세스할 수 있는 개별적으로 초기화된 자체 변수 인스턴스를 갖는다는 점에서 일반 변수와 다릅니다.

각 스레드, 즉 Thread 클래스의 인스턴스 에는 연결된 ThreadLocal 변수 의 맵이 있습니다 . 맵의 키는 ThreadLocal 개체 에 대한 참조 이고 값은 "획득한" ThreadLocal 변수에 대한 참조입니다.

Random 클래스가 다중 스레드 응용 프로그램에서 난수를 생성하는 데 적합하지 않은 이유는 무엇입니까?

Random 클래스를 사용하여 난수를 얻습니다. 하지만 멀티스레드 환경에서도 제대로 작동할까요? 사실, 아니오. Random은 다중 스레드 환경에 적합하지 않습니다. 여러 스레드가 동시에 클래스에 액세스하면 성능이 저하되기 때문입니다.

이 문제를 해결하기 위해 JDK 7에서는 다중 스레드 환경에서 난수를 생성하는 java.util.concurrent.ThreadLocalRandom 클래스를 도입했습니다. ThreadLocalRandom 의 두 클래스로 구성됩니다 .

한 스레드에서 수신한 난수는 다른 스레드와 독립적이지만 java.util.Random은 전역적으로 난수를 제공합니다. 또한 Random 과 달리 ThreadLocalRandom은 명시적 시드를 지원하지 않습니다. 대신 Random 에서 상속된 setSeed() 메서드를 재정의하므로 호출 시 항상 UnsupportedOperationException이 발생 합니다 .

ThreadLocalRandom 클래스 의 메서드를 살펴보겠습니다 .

방법 행동
ThreadLocalRandom 전류() 현재 스레드의 ThreadLocalRandom을 반환합니다.
정수 다음(정수 비트) 다음 의사 난수를 생성합니다.
double nextDouble(더블 최소, 더블 바운드) 최소 (포함)와 바운드 (제외) 사이의 균등 분포에서 의사 난수를 반환합니다 .
int nextInt(적어도 정수, 경계 정수) 최소(포함)와 바운드(제외) 사이의 균등 분포에서 의사 난수를 반환합니다.
긴 nextLong(긴 n) 0(포함)과 지정된 값(제외) 사이의 균일 분포에서 의사 난수를 반환합니다.
긴 nextLong(긴 최소, 긴 경계) 최소(포함)와 바운드(제외) 사이의 균등 분포에서 의사 난수를 반환합니다.
무효 setSeed(긴 시드) UnsupportedOperationException 을 던집니다 . 이 생성기는 시드를 지원하지 않습니다.

ThreadLocalRandom.current()를 사용하여 난수 얻기

ThreadLocalRandom은 ThreadLocal Random 클래스 의 조합입니다. Random 클래스 의 인스턴스에 대한 동시 액세스를 단순히 피함으로써 다중 스레드 환경에서 더 나은 성능을 얻습니다.

여러 스레드가 포함된 예제를 구현하고 응용 프로그램이 ThreadLocalRandom 클래스로 수행하는 것을 살펴보겠습니다.


import java.util.concurrent.ThreadLocalRandom;

class RandomNumbers extends Thread {

    public void run() {
        try {
            int bound = 100;
            int result = ThreadLocalRandom.current().nextInt(bound);
            System.out.println("Thread " + Thread.currentThread().getId() + " generated " + result);
        }
        catch (Exception e) {
            System.out.println("Exception");
        }
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

				for (int i = 0; i < 10; i++) {
            RandomNumbers randomNumbers = new RandomNumbers();
            randomNumbers.start();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("Time taken: " + (endTime - startTime));
    }
}

우리 프로그램의 결과:

소요 시간: 1
스레드 17 생성 13
스레드 18 생성 41
스레드 16 생성 99
스레드 19 생성 25
스레드 23 생성 33
스레드 24 생성 21
스레드 15 생성 15
스레드 21 생성 28
스레드 22 생성 97
스레드 20 생성 33

이제 RandomNumbers 클래스를 변경하고 Random을 사용하겠습니다 .


int result = new Random().nextInt(bound);
소요 시간: 5
스레드 20 생성 48
스레드 19 생성 57
스레드 18 생성 90
스레드 22 생성 43
스레드 24 생성 7
스레드 23 생성 63
스레드 15 생성 2
스레드 16 생성 40
스레드 17 생성 29
스레드 21 생성 12

필기 해! 테스트에서 결과가 같을 때도 있고 다를 때도 있었습니다. 그러나 더 많은 스레드(예: 100)를 사용하면 결과는 다음과 같습니다.

무작위 — 19-25ms
ThreadLocalRandom — 17-19ms

따라서 응용 프로그램의 스레드가 많을수록 다중 스레드 환경에서 Random 클래스 를 사용할 때 성능 저하가 커집니다 .

Random 클래스 와 ThreadLocalRandom 클래스 간의 차이점을 요약하고 반복하려면 다음을 수행하십시오 .

무작위의 스레드로컬랜덤
다른 스레드가 동일한 Random 인스턴스를 사용하면 충돌이 발생하고 성능이 저하됩니다. 생성된 난수가 현재 스레드에 로컬이기 때문에 충돌이나 문제가 없습니다.
선형 합동 공식을 사용하여 초기값을 변경합니다. 난수 생성기는 내부적으로 생성된 시드를 사용하여 초기화됩니다.
각 스레드가 고유한 Random 개체 집합을 사용하는 응용 프로그램에 유용합니다 . 여러 스레드가 스레드 풀에서 병렬로 난수를 사용하는 애플리케이션에 유용합니다.
이것은 부모 클래스입니다. 이것은 자식 클래스입니다.