[java] Java ThreadLocal 변수가 정적이어야하는 이유

여기에서 Threadlocal에 대한 JavaDoc을 읽었습니다.

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

“ThreadLocal 인스턴스는 일반적으로 상태를 스레드 (예 : 사용자 ID 또는 트랜잭션 ID)와 연관시키려는 클래스의 비공개 정적 필드입니다.”

하지만 내 질문은 왜 정적으로 만들기로 선택 했는가입니다. (일반적으로) “스레드 당”상태를 갖는 것이 약간 혼란 스럽지만 필드는 정적입니까?



답변

인스턴스 레벨 필드라면 보장 된 “스레드 당”이 아니라 실제로 “스레드 당-인스턴스 당”이됩니다. 그것은 일반적으로 당신이 찾고있는 의미가 아닙니다.

일반적으로 사용자 대화, 웹 요청 등으로 범위가 지정된 개체와 같은 것을 보유하고 있습니다. 클래스 인스턴스에 하위 범위가 지정되는 것을 원하지 않습니다.
하나의 웹 요청 => 하나의 지속성 세션.
하나의 웹 요청 => 객체 당 하나의 지속성 세션이 아닙니다.


답변

정적으로 만들거나 클래스의 정적 필드를 피하려는 경우 클래스 자체를 싱글 톤으로 만든 다음 해당 싱글 톤을 전역 적으로 사용할 수있는 한 인스턴스 수준 ThreadLocal을 안전하게 사용할 수 있습니다.


답변

그럴 필요는 없습니다. 중요한 것은 싱글 톤이어야한다는 것입니다.


답변

그 이유는 스레드와 관련된 포인터를 통해 변수에 액세스하기 때문입니다. 스레드 범위가있는 전역 변수처럼 작동하므로 정적이 가장 적합합니다. 이것은 pthreads와 같은 것들에서 스레드 로컬 상태를 얻는 방법이므로 이것은 단지 역사와 구현의 우연 일 수 있습니다.


답변

스레드 당 인스턴스별로 스레드 로컬을 사용하는 것은 객체의 모든 메서드에서 무언가를 표시하고 일반 필드에서와 같이 액세스를 동기화하지 않고 스레드로부터 안전하게 유지하려는 경우입니다.


답변

이것을 참조하면 더 나은 이해를 얻을 수 있습니다.

간단히 말해서 ThreadLocal객체는 키-값 맵처럼 작동합니다. 스레드가 ThreadLocal get/set메소드를 호출 할 때 맵의 키에있는 스레드 객체와 맵의 값에있는 값을 검색 / 저장합니다. 이것이 다른 스레드가 다른 맵의 항목에 상주하기 때문에 다른 스레드가 다른 값 사본 (로컬에 저장하려는)을 갖는 이유입니다.

그렇기 때문에 모든 값을 유지하려면 하나의 맵만 필요합니다. 필요하지는 않지만 각 스레드 객체를 유지하기 위해 여러 맵 (정적 선언없이)을 가질 수 있습니다. 이는 완전히 중복되기 때문에 정적 변수가 선호되는 이유입니다.


답변

static final ThreadLocal 변수는 스레드로부터 안전합니다.

staticThreadLocal 변수를 각 스레드에 대해서만 여러 클래스에서 사용할 수 있도록합니다. 여러 클래스에 걸쳐 각 스레드 로컬 변수의 일종의 전역 변수 탈퇴입니다.

다음 코드 샘플을 사용하여이 스레드 안전성을 확인할 수 있습니다.

  • CurrentUser -ThreadLocal에 현재 사용자 ID를 저장합니다.
  • TestService-메서드가있는 간단한 서비스 getUser()-CurrentUser에서 현재 사용자를 가져옵니다.
  • TestThread -이 클래스는 여러 스레드를 생성하고 동시에 사용자 ID를 설정하는 데 사용됩니다.

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

TestThread 메인 클래스를 실행합니다. 출력-

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

분석 요약

  1. “main”스레드가 시작되고 현재 사용자를 “62”로 ​​설정, 병렬 “ForkJoinPool.commonPool-worker-2″스레드가 시작되고 현재 사용자를 “31”로 설정, 병렬 “ForkJoinPool.commonPool-worker-3″스레드가 시작되고 현재 사용자로 설정 사용자를 “81”로, 병렬 “ForkJoinPool.commonPool-worker-1″스레드가 시작되고 현재 사용자를 “87”로 설정합니다. Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. 위의 모든 스레드는 3 초 동안 휴면합니다.
  3. main실행이 종료되고 현재 사용자를 “62”로 ​​인쇄, 병렬 ForkJoinPool.commonPool-worker-1실행이 종료되고 현재 사용자를 “87”로 인쇄, 병렬 ForkJoinPool.commonPool-worker-2실행이 종료되고 현재 사용자가 “31”로 인쇄, 병렬 ForkJoinPool.commonPool-worker-3실행이 종료되고 현재 사용자가 “81”로 인쇄됩니다.

추론

동시 스레드는 “정적 최종 ThreadLocal”로 선언 된 경우에도 올바른 사용자 ID를 검색 할 수 있습니다.