언제 그리고 어떻게 ThreadLocal 변수를 사용해야합니까?


876

언제 ThreadLocal변수를 사용해야 합니까?

어떻게 사용 되나요?


11
ThreadLocal을 사용하는 경우 절대 래퍼를 쓰지 마십시오 !!! 변수를 사용하려는 모든 개발자는 변수가 'ThreadLocal'이라는 것을 알아야합니다.
버그 아님

2
@Notabug 명시 적이라면 ThreadLocal 값을 처리하는 변수의 이름을 지정할 수 있습니다 (예 :) RequestUtils.getCurrentRequestThreadLocal(). 비록 이것이 매우 우아하다고 말하지는 않지만, 이것은 ThreadLocal 자체가 대부분의 경우에 매우 우아하지 않다는 사실에서 비롯됩니다.
whirlwin

@Sasha는 ThreadLocal를 매장 실행 추적에 사용할 수 있습니다
gaurav

답변:


864

스레드에 안전하지 않은 객체가 있지만 해당 객체에 대한 액세스를 동기화 하지 않으려는 경우 (일반적으로) 사용할 수 있습니다 ( SimpleDateFormat ). 대신 각 스레드에 고유 한 개체 인스턴스를 제공하십시오.

예를 들면 다음과 같습니다.

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

문서 .


160
동기화 또는 threadlocal의 또 다른 대안은 변수를 로컬 변수로 만드는 것입니다. 지역 변수는 항상 스레드 안전합니다. DateFormats를 만드는 데 비용이 많이 들기 때문에 DateFormats를 로컬로 만드는 것은 나쁜 습관이라고 생각하지만이 주제에 대한 확실한 지표는 보지 못했습니다.
Julien Chastang

18
이것은 해킹에 지불하는 높은 가격 SimpleDateFormat입니다. 아마도 스레드 안전 대안 을 사용 하는 것이 좋습니다 . 당신이 동의하는 경우 싱글 나쁜ThreadLocal더 악화입니다.
Alexander Ryzhov 2016 년

4
@overthink ThreadLocal을 정적이고 최종적으로 선언 할 이유가 있습니까?
Sachin Gorade

4
ThreadLocal.get () 메소드는 각 스레드에 대해 ThreadLocal.initialValue ()를 한 번 호출합니다. 이는 각 스레드에 대해 SimpleDateFormat 오브젝트가 작성됨을 의미합니다. 가비지 수집 문제를 처리 할 필요가 없기 때문에 SimpleDateFormat을 로컬 변수로 사용하는 것이 더 좋지 않습니까?
sudeepdino008

3
클래스 로더를 가비지 수집 할 수 없도록 익명 클래스가 작성되므로 SimpleDateFormat에 이중 중괄호 초기화를 사용하지 않도록주의하십시오. 메모리 누수 : 새 SimpleDateFormat ()을 반환합니다. {{applyPattern ( "yyyyMMdd HHmm")}};
user1944408

427

a ThreadLocal는 지정된 내의 데이터에 대한 참조 이므로 스레드 풀을 사용하는 응용 프로그램 서버에서 s를 Thread사용할 때 클래스 로딩 누수가 발생할 수 ThreadLocal있습니다. 당신은 어떤 청소에 대해 매우 신중해야 ThreadLocal당신을들 get()또는 set()사용하여 ThreadLocalremove()방법을.

완료 할 때 정리하지 않으면 배포 된 웹앱의 일부로로드 된 클래스에 대한 참조는 영구 힙에 유지되며 가비지 수집을받지 않습니다. webapp을 재배포 / 배포 해제하면 webapp이 소유 한 것이 아니기 Thread때문에 webapp의 클래스에 대한 각 참조를 정리 Thread하지 않습니다. 각 연속 배포는 가비지 수집되지 않는 클래스의 새 인스턴스를 만듭니다.

java.lang.OutOfMemoryError: PermGen space일부 인터넷 검색 으로 인해 -XX:MaxPermSize버그가 수정되지 않고 메모리 부족 예외가 발생할 수 있습니다.

이러한 문제점이 발생하면 Eclipse의 메모리 분석기 를 사용 하거나 Frank Kieviet의 안내서후속 조치를 통해 이러한 참조를 보유하는 스레드 및 클래스를 판별 할 수 있습니다 .

업데이트 : Alex Vasseur의 블로그 항목 을 다시 발견 하여 ThreadLocal내가 겪고 있는 문제를 추적하는 데 도움 이되었습니다.


11
Alex Vasseur는 블로그를 옮겼습니다. 다음 은 메모리 누수 기사에 대한 현재 링크입니다.
Kenster

12
Julien이 연결하는 실이 여기 로 이동했으며 읽을 가치
Donal Fellows

28
이것은 정보를 제공하지만 실제로 어떤 식 으로든 질문에 대답하지 않는 대답에 대한 많은 찬사입니다.
Robin

8
이제 PermGen이 Java 8에 의해 종료되었으므로 어떤 식 으로든이 답변이 변경됩니까?
사악한 세탁기

13
@Robin : 동의하지 않습니다. 문제는 ThreadLocal을 올바르게 사용하는 방법, 어떤 개념 (ThreadLocal here)을 완전히 이해하기 위해 그것을 사용하지 않는 방법과 그것을 부주의하게 사용하는 위험을 이해하는 것이 중요하며 이것이 Phil의 대답입니다. 다른 답변에서는 다루지 않은 점을 다행스럽게 생각합니다. 모든 투표는 가치가 있습니다. 나는 단지 품질 보증 사이트가 아닌 개념에 대한 이해를 구축해야한다고 생각합니다.
Saurabh Patil

166

많은 프레임 워크는 ThreadLocals를 사용하여 현재 스레드와 관련된 일부 컨텍스트를 유지합니다. 예를 들어, 현재 트랜잭션이 ThreadLocal에 저장 될 때 스택을 다운받는 누군가가 액세스해야 할 경우를 대비하여 모든 메소드 호출을 통해 매개 변수로 전달할 필요는 없습니다. 웹 응용 프로그램은 현재 요청 및 세션에 대한 정보를 ThreadLocal에 저장하여 응용 프로그램이 쉽게 액세스 할 수 있도록합니다. Guice를 사용 하면 주입 된 객체에 대한 사용자 지정 범위 를 구현할 때 ThreadLocals를 사용할 수 있습니다 (Guice의 기본 서블릿 범위는 대부분 사용합니다).

ThreadLocals는 일종의 전역 변수 (하나의 스레드로 제한되기 때문에 약간 덜 악하지만), 원하지 않는 부작용과 메모리 누수를 피하기 위해 사용할 때주의해야합니다. ThreadLocal 값이 더 이상 필요하지 않을 때 항상 자동으로 지워지고 API를 잘못 사용할 수 없도록 API를 설계하십시오 (예 : ). ThreadLocals를 사용하여 코드를 더 깨끗하게 만들 수 있으며, 드물게 작동하는 유일한 방법입니다. 현재 프로젝트에는 두 가지 경우가 있습니다. 여기 에는 "정적 필드 및 전역 변수"에 설명되어 있습니다.


4
이것이 바로 exPOJO 프레임 워크 (www.expojo.com)가 주석과 주입의 오버 헤드없이 ORM Session / PersistenceManager에 액세스하는 방법입니다. '객체 주입'대신 '스레드 주입'과 비슷합니다. 종속성이 필요할 수있는 모든 개체에 포함시킬 필요없이 (및 오버 헤드없이) 종속성에 대한 액세스를 제공합니다. 클래식 DI (예 : Spring 등) 대신 스레드 인젝션을 사용할 때 DI 프레임 워크를 만드는 '가벼운'방법은 놀랍습니다.
Volksman

27
이 필수 답변을 찾기 위해 왜 지금까지 스크롤을 내려야합니까!?
제레미 스타 인

1
@Esko, 프로젝트에서 해시 코드와 동등에서 ThreadLocal을 사용할 필요가 없습니다. 더 좋은 방법은 필요할 때마다 해시 코드 / 동일 함수에 대한 참조를 작성하거나 전달하는 것입니다. 이런 식으로 ThreadLocal 핵의 필요성을 완전히 피할 수 있습니다.
Pacerier


1
이것이 TL 사용에 대한 가장 좋은 설명입니다.
Dexter

52

Java에서 스레드마다 다를 수있는 데이텀이있는 경우 해당 데이텀을 필요한 (또는 필요할 수있는) 모든 메소드로 전달하거나 데이텀을 스레드와 연관시키는 선택이 있습니다. 모든 방법이 이미 공통 "컨텍스트"변수를 통과해야하는 경우 모든 곳에서 데이텀을 전달하는 것이 가능할 수 있습니다.

그렇지 않은 경우 추가 매개 변수로 메소드 서명을 어지럽히 지 않을 수 있습니다. 스레드되지 않은 세계에서는 전역 변수에 해당하는 Java 문제를 해결할 수 있습니다. 스레드 단어에서 전역 변수에 해당하는 것은 스레드 로컬 변수입니다.


13
따라서 전역을 피하는 것과 같은 방식으로 스레드 지역을 피해야합니다. 값을 전달하는 대신 전역 (스레드 로컬)을 만드는 것이 좋다는 것을 받아 들일 수는 없습니다. 사람들은 종종 수정하고 싶지 않은 아키텍처 문제를 드러내 기 때문에 좋아하지 않습니다.
Juan Mendes

10
아마도 ... 세션 컨텍스트, DB 트랜잭션, 로그인 한 사용자 등 모든 곳 에서 전달해야하는 새로운 데이텀을 추가해야하는 거대한 기존 코드베이스가있는 경우 유용 할 수 있습니다. .
코르 넬 메이슨

6
데이터 대신 데이텀을 사용하는 것이 좋습니다.
깊은

이것은 나를 궁금하게했다. 'datum' is the singular form and 'data' is the plural form.
Siddhartha

23

Java Concurrency in Practice 책에는 좋은 예가 있습니다. 저자 ( Joshua Bloch )는 스레드 제한이 스레드 안전성을 달성하는 가장 간단한 방법 중 하나 인 방법을 설명하고 ThreadLocal 은 스레드 제한을 유지하는보다 공식적인 방법입니다. 결국 그는 사람들이 그것을 글로벌 변수로 사용하여 어떻게 남용 할 수 있는지 설명합니다.

언급 한 책에서 텍스트를 복사했지만 ThreadLocal을 사용해야하는 곳을 이해하는 것이별로 중요하지 않으므로 코드 3.10이 누락되었습니다.

스레드 로컬 변수는 종종 가변 단일 톤 또는 전역 변수를 기반으로하는 설계에서 공유를 방지하는 데 사용됩니다. 예를 들어 단일 스레드 응용 프로그램은 시작시 초기화되는 전역 데이터베이스 연결을 유지하여 모든 메서드에 Connection을 전달하지 않아도됩니다. JDBC 연결은 스레드로부터 안전하지 않을 수 있으므로 추가 조정없이 전역 연결을 사용하는 다중 스레드 응용 프로그램도 스레드로부터 안전하지 않습니다. Listing 3.10의 ConnectionHolder에서와 같이 ThreadLocal을 사용하여 JDBC 연결을 저장하면 각 스레드는 자체 연결을 갖게된다.

ThreadLocal은 응용 프로그램 프레임 워크 구현에 널리 사용됩니다. 예를 들어, J2EE 컨테이너는 EJB 호출 기간 동안 트랜잭션 컨텍스트를 실행 스레드와 연관시킵니다. 이것은 트랜잭션 컨텍스트를 보유하는 정적 Thread-Local을 사용하여 쉽게 구현됩니다. 프레임 워크 코드는 현재 실행중인 트랜잭션을 결정해야 할 때이 ThreadLocal에서 트랜잭션 컨텍스트를 가져옵니다. 이는 실행 컨텍스트 정보를 모든 메소드에 전달할 필요가 줄어들지 만이 메커니즘을 사용하는 모든 코드를 프레임 워크에 결합한다는 점에서 편리합니다.

스레드 제한 속성을 전역 변수 사용 라이센스로 처리하거나 "숨겨진"메서드 인수를 만드는 수단으로 ThreadLocal을 악용하기 쉽습니다. 전역 변수와 마찬가지로 스레드 지역 변수는 재사용 성을 떨어 뜨리고 클래스간에 숨겨진 커플 링을 유발할 수 있으므로주의해서 사용해야합니다.


18

기본적으로, 당신은 필요로 할 때 , 현재의 thread에 의존 변수의 값을 하고 당신이 다른 방법으로 스레드에 가치를 부여하는 편리하지 않습니다 (스레드를 서브 클래스, 예를 들어).

일반적인 경우는 다른 프레임 워크가 스레드를 생성 한 경우입니다. 코드가 실행중인 예 : 서블릿 컨테이너)를 생성 한 경우 또는 변수가 "논리적 위치"(변수가 아닌) "논리적 위치"에 있기 때문에 ThreadLocal을 사용하는 것이 더 의미가있는 경우입니다. Thread 서브 클래스 또는 다른 해시 맵에 매달려 있습니다).

내 웹 사이트에서 ThreadLocal언제 사용 해야하는지에 대한 추가 토론과 예가 있습니다.

어떤 사람들은 스레드 번호가 필요한 특정 동시 알고리즘에서 각 스레드에 "스레드 ID"를 연결하는 방법으로 ThreadLocal을 사용하도록 권장합니다 (예 : Herlihy & Shavit 참조). 그러한 경우, 실제로 혜택을 받고 있는지 확인하십시오!


있어 ThreadLocal 매장 실행 추적에 사용할 수 있습니다
gaurav

13
  1. Java의 ThreadLocal은 JDK 1.2에 도입되었지만 나중에 JDK 1.5에서 생성되어 ThreadLocal 변수에 유형 안전성을 도입했습니다.

  2. ThreadLocal은 Thread 범위와 연관 될 수 있습니다. Thread에 의해 실행되는 모든 코드는 ThreadLocal 변수에 액세스 할 수 있지만 두 개의 스레드는 서로 스레드 ThreadLocal 변수를 볼 수 없습니다.

  3. 각 스레드에는 ThreadLocal 변수의 독점 사본이 있습니다. 스레드 로컬 변수에는 스레드가 완료되거나 종료 된 후 (일반적으로 또는 예외로 인해) 가비지 콜렉션에 적합하게됩니다. ThreadLocal 변수에는 다른 라이브 참조가 없습니다.

  4. Java의 ThreadLocal 변수는 일반적으로 클래스의 전용 정적 필드이며 Thread 내에서 상태를 유지합니다.

더 읽기 : Java의 ThreadLocal-예제 프로그램 및 학습서


있어 ThreadLocal 매장 실행 추적에 사용할 수 있습니다
gaurav

11

이 문서 는 다음과 같이 잘 설명합니다. "[스레드 로컬 변수]에 액세스하는 각 스레드 (get 또는 set 메소드를 통해)에는 자체적으로 독립적으로 초기화 된 변수 사본이 있습니다."

각 스레드에 고유 한 사본이 있어야 할 때 하나를 사용하십시오. 기본적으로 데이터는 스레드간에 공유됩니다.


18
기본적으로 정적 객체 또는 두 스레드가 동일한 객체에 대한 참조를 갖는 스레드간에 명시 적으로 전달 된 객체는 공유됩니다. 스레드에서 로컬로 선언 된 객체는 공유되지 않습니다 (스레드의 스택에 로컬입니다). 그것을 명확히하고 싶었습니다.
Ian Varley

@IanVarley : 지적 해 주셔서 감사합니다. 따라서 MyThread 클래스에서 선언되고 사용되는 변수가 있다면 그 경우 ThreadLocal이 필요합니까? 예 : 클래스 MyThread는 Thread {String를 확장합니다. var1 ;, int var2; }이 경우 var1 및 var2는 스레드 자체 스택의 일부이며 공유되지 않으므로이 경우 ThreadLocal이 필요합니까? 이해가 완전히 잘못되었을 수 있으므로 안내하십시오.
Jayesh

4
각 스레드가 고유 한 사본을 원할 경우 로컬로 선언 할 수없는 이유는 무엇입니까 (항상 스레드 안전)?
sudeepdino008

@ sudeepdino008 당신은 항상 그 스레드 (webapp 프레임 워크, 스레드 풀 라이브러리)의 생성을 제어 할 수있는 것은 아닙니다
JMess

10

Webapp 서버는 스레드 풀을 유지할 수 ThreadLocal있으며 클라이언트에 응답하기 전에 var를 제거해야하므로 다음 요청에서 현재 스레드를 재사용 할 수 있습니다.


8

threadlocal 변수를 사용할 수있는 두 가지 사용 사례
-1- 상태를 스레드와 연결해야하는 경우 (예 : 사용자 ID 또는 트랜잭션 ID) 이는 일반적으로 서블릿으로가는 모든 요청에 ​​고유 한 transactionID가있는 웹 응용 프로그램에서 발생합니다.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

여기서 initial을 사용한 메소드는 람다 식을 사용하여 구현됩니다.
2- 또 다른 유스 케이스는 스레드 안전 인스턴스를 원하고 동기화 성능 비용이 높기 때문에 동기화를 사용하지 않는 경우입니다. 이러한 경우 중 하나가 SimpleDateFormat을 사용하는 경우입니다. SimpleDateFormat은 스레드로부터 안전하지 않으므로 스레드를 안전하게 만드는 메커니즘을 제공해야합니다.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}

모든 목적이 증분 고유 정수 값을 반환하는 경우 ThreadLocal을 사용하여 예제 1에서 고유 ID를 생성하는 이점을 여전히 보지 못했습니다. ```public class ThreadId {// 할당 될 다음 스레드 ID를 포함하는 원자 정수 private final final AtomicInteger nextId = new AtomicInteger (0); // 필요한 경우 할당하여 현재 스레드의 고유 ID를 반환합니다. public static int get () {return nextId..getAndIncrement (); }}```
dashenswen

7

Java 8 릴리스부터 초기화하는 더 선언적인 방법이 있습니다 ThreadLocal.

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

Java 8 릴리스까지 다음을 수행해야했습니다.

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

또한 사용되는 클래스의 인스턴스화 메소드 (생성자, 팩토리 메소드)가 ThreadLocal매개 변수를 사용 하지 않으면 메소드 참조 (Java 8에서 도입 됨)를 사용할 수 있습니다.

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

참고 : 호출되었지만 값이 이전에 평가되지 않은 java.util.function.Supplier경우에만 평가되는 람다를 전달하므로 평가가 지연 ThreadLocal#get됩니다.


5

ThreadLocal 패턴에 매우주의해야합니다. Phil과 같은 주요 단점이 있지만 언급되지 않은 것은 ThreadLocal 컨텍스트를 설정하는 코드가 "재진입"하지 않도록하는 것입니다.

정보를 설정하는 코드가 두 번째 또는 세 번째로 실행될 때 예상치 못한 스레드 정보가 변경 될 수 있기 때문에 나쁜 일이 발생할 수 있습니다. 따라서 다시 설정하기 전에 ThreadLocal 정보가 설정되지 않았는지 확인하십시오.


2
코드를 처리 할 준비가되어 있으면 재진입은 문제가되지 않습니다. 시작시 변수가 이미 설정되어 있는지 확인하고 종료시 이전 값을 복원하거나 (있는 경우) 제거하십시오 (없는 경우).
supercat

1
@Jeff, Dude, ThreadLocal패턴 뿐만 아니라 작성하는 모든 코드에도 적용됩니다 . 당신이 F(){ member=random(); F2(); write(member); }하고 F2가 새로운 값으로 멤버를 재정의하면 분명히 write(member)더 이상 당신이 작성한 번호를 쓰지 않을 것 random()입니다. 이것은 말 그대로 상식입니다. 마찬가지로, 그렇게하면 F(){ F(); }무한 루프로 행운을 빕니다! 이것은 모든 곳에서 사실이며 특정하지 않습니다 ThreadLocal.
Pacerier

@ 제프, 코드 예제?
Pacerier

5

언제?

객체가 스레드로부터 안전하지 않은 경우, 확장 성을 방해하는 동기화 대신 모든 객체에 하나의 객체를 제공하고 스레드 범위 (ThreadLocal)를 유지합니다. 가장 안전하지만 스레드로부터 안전하지 않은 오브젝트 중 하나는 데이터베이스 연결 및 JMSConnection입니다.

어떻게 ?

한 가지 예는 Spring 프레임 워크가 ThreadLocal 변수에 이러한 연결 오브젝트를 유지하여 백그라운드에서 트랜잭션을 관리하기 위해 ThreadLocal을 많이 사용하는 것입니다. 높은 수준에서 트랜잭션이 시작되면 연결을 얻고 자동 커밋을 비활성화하고 ThreadLocal에 유지합니다. 추가 DB 호출에서 동일한 연결을 사용하여 db와 통신합니다. 결국, ThreadLocal에서 연결을 가져 와서 트랜잭션을 커밋 (또는 롤백)하고 연결을 해제합니다.

log4j는 MDC를 유지하기 위해 ThreadLocal을 사용한다고 생각합니다.


5

ThreadLocal 다른 스레드간에 공유해서는 안되는 상태를 원하지만 전체 수명 동안 각 스레드에서 액세스 할 수 있어야하는 경우 유용합니다.

예를 들어, 각 요청이 다른 스레드에서 제공되는 웹 응용 프로그램을 상상해보십시오. 각 요청마다 여러 번의 데이터 조각이 필요하다고 가정하면 계산하는 데 비용이 많이 듭니다. 그러나 들어오는 각 요청에 대해 해당 데이터가 변경되었을 수 있으므로 일반 캐시를 사용할 수 없습니다. 이 문제에 대한 간단하고 빠른 해결책은이 ThreadLocal데이터에 대한 액세스 권한을 보유한 변수를 사용하여 각 요청에 대해 한 번만 계산해야합니다. 물론이 문제는 다음을 사용하지 않고도 해결할 수 있습니다.ThreadLocal 있지만 설명을 위해 고안했습니다.

ThreadLocals는 본질적으로 세계 국가의 한 형태 라는 것을 명심하십시오 . 결과적으로 다른 영향을 많이 미치므로 가능한 모든 다른 솔루션을 고려한 후에 만 ​​사용해야합니다.


ThreadLocals는 전역 상태가 아닌 한 전역 상태가 아닙니다. 실제로 항상 액세스 가능한 스택 리소스입니다. 모든 메소드에서 해당 변수를 전달하여 지연된 스레드 로컬을 시뮬레이션 할 수 있습니다 (지연됨). 그것은 세계 상태를 만들지 않습니다 ...
Enerccio

코드의 어느 곳에서나 (같은 스레드 컨텍스트에서) 액세스 할 수 있다는 의미에서 전역 상태의 한 형태입니다. 누가이 값을 읽고 쓰는지에 대해 추론 할 수없는 것과 같은 모든 영향이 수반됩니다. 함수 매개 변수를 사용하는 것은 지체되지 않으며 깨끗한 인터페이스를 장려하는 것이 좋습니다. 그러나 코드베이스의 전체 깊이에 매개 변수를 전달하면 코드 냄새가 나는 것으로 동의합니다. 그러나 많은 경우에 ThreadLocal을 사용하면 처음에는 코드 냄새가 나는 것으로 생각합니다. 따라서 이것을 고려해야합니다.
Dimos

단순히 객체 상태의 일부일 수 있으며 전역 적으로 사용할 필요는 없습니다. 물론 약간의 오버 헤드가 발생하지만 여러 객체가 스레드마다 다른 상태를 가져야하는 ThreadLocal경우 객체 필드로 사용할 수 있습니다 .
Enerccio

네가 옳아. ThreadLocal전 세계에서 액세스 할 수있는 의 여러 가지 오용을 우연히 발견했을 것입니다 . 말했듯이 가시성을 제한하는 클래스 필드로 계속 사용할 수 있습니다.
Dimos September

4

여기에 새로운 것은 없지만 오늘 ThreadLocal웹 응용 프로그램에서 Bean Validation을 사용할 때 매우 유용하다는 것을 알았습니다 . 유효성 검사 메시지는 현지화되어 있지만 기본적으로을 사용 Locale.getDefault()합니다. 를 Validator다른 것으로 구성 할 수 MessageInterpolator있지만 Locale호출 할 때 시간 을 지정할 수있는 방법이 없습니다 validate. 따라서 정적 ThreadLocal<Locale>(또는 더 나은 아직 다른 것들이있는 일반 컨테이너를 ThreadLocal만든 다음 사용자 정의를 MessageInterpolator선택할 수 Locale있습니다. 다음 단계는 ServletFilter세션 값을 사용하거나 request.getLocale()로케일 및 저장소를 선택하는 것을 작성하는 것 입니다 그것은 당신에 ThreadLocal대한 참조입니다.


4

@unknown (google)에서 언급했듯이 참조 된 값이 각 스레드에서 고유 할 수있는 전역 변수를 정의하는 것이 사용법입니다. 일반적으로 현재 실행 스레드에 연결된 일종의 상황 정보를 저장하는 것이 사용됩니다.

Java EE 환경에서이를 사용하여 Java EE를 인식하지 못하는 클래스 (HttpSession 또는 EJB SessionContext에 액세스 할 수 없음)에 사용자 ID를 전달합니다. 이런 식으로 보안 기반 작업에 ID를 사용하는 코드는 모든 메소드 호출에서 명시 적으로 전달하지 않고도 어디서나 ID에 액세스 할 수 있습니다.

대부분의 Java EE 호출에서 요청 / 응답주기는 이러한 유형의 사용법을 쉽게 만들어 ThreadLocal을 설정 및 설정 해제 할 수있는 잘 정의 된 시작 및 종료 지점을 제공합니다.


4

ThreadLocal은 동기화되지 않은 메소드의 여러 스레드에 의해 변경 가능한 오브젝트에 대한 액세스를 보장하므로 변경 가능한 오브젝트를 메소드 내에서 변경할 수 없게합니다.

이것은 각 스레드에 대해 변경 가능한 객체의 새로운 인스턴스를 제공함으로써 달성됩니다. 따라서 각 스레드에 대한 로컬 사본입니다. 이것은 로컬 변수처럼 액세스 할 수있는 메소드에서 인스턴스 변수를 만드는 데 약간의 해킹입니다. 메소드 로컬 변수는 스레드에서만 사용할 수 있다는 것을 알고 있으므로 한 가지 차이점이 있습니다. 일단 메소드 실행이 끝나면 thread 로컬에서 공유 할 수있는 가변 객체가 여러 메소드에서 정리 될 때까지 메소드 로컬 변수를 사용할 수 없습니다.

정의에 의해 :

Java의 ThreadLocal 클래스를 사용하면 동일한 스레드에서만 읽고 쓸 수있는 변수를 작성할 수 있습니다. 따라서 두 스레드가 동일한 코드를 실행하고 코드에 ThreadLocal 변수에 대한 참조가 있어도 두 스레드는 서로의 ThreadLocal 변수를 볼 수 없습니다.

ThreadJava의 각각 에는 포함 ThreadLocalMap되어 있습니다.
어디

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

ThreadLocal 달성 :

이제 아래와 같이 (와 함께 또는없이 initialValue()) 변경 가능한 객체를 보유 할 ThreadLocal에 대한 래퍼 클래스를 만듭니다 .
이제이 래퍼의 getter와 setter는 변경 가능한 객체 대신 threadlocal 인스턴스에서 작동합니다.

threadlocal의 getter ()가의 threadlocalmap에서 값을 찾지 못한 경우 Thread; 그런 다음 initialValue ()를 호출하여 스레드와 관련하여 개인용 사본을 가져옵니다.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

이제 wrapper.getDateFormatter()호출 threadlocal.get()하고 이것이 (threadlocal) 인스턴스를 currentThread.threadLocalMap포함 하는지 확인합니다 . yes 인 경우 해당 threadlocal 인스턴스에 대한 값 (SimpleDateFormat)을 리턴하고 그렇지 않으면이 threadlocal 인스턴스 인 initialValue ()로 맵을 추가하십시오.


이 변경 가능한 클래스에서 스레드 안전성을 달성했습니다. 각 스레드별로 자체 가변 인스턴스로 작업하지만 동일한 ThreadLocal 인스턴스로 작업합니다. 의미 모든 스레드는 키와 동일한 ThreadLocal 인스턴스를 공유하지만 값과는 다른 SimpleDateFormat 인스턴스를 공유합니다.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java


3

스레드 로컬 변수는 종종 가변 단일 톤 또는 전역 변수를 기반으로하는 설계에서 공유를 방지하는 데 사용됩니다.

연결 풀을 사용하지 않을 때 각 스레드에 대해 별도의 JDBC 연결을 만드는 것과 같은 시나리오에서 사용할 수 있습니다.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

public static Connection getConnection() {
      return connectionHolder.get();
} 

getConnection을 호출하면 해당 스레드와 관련된 연결이 반환됩니다. 스레드와 공유하고 싶지 않은 dateformat, 트랜잭션 컨텍스트와 같은 다른 속성을 사용하여 동일한 연결을 수행 할 수 있습니다.

로컬 변수를 동일하게 사용할 수도 있지만 이러한 리소스는 일반적으로 생성하는 데 시간이 걸리므로 비즈니스 로직을 수행 할 때마다 다시 생성하지 않으려는 경우가 있습니다. 그러나 ThreadLocal 값은 스레드 객체 자체에 저장되며 스레드가 가비지 수집되는 즉시 이러한 값도 사라집니다.

링크 는 ThreadLocal 사용법을 잘 설명합니다.


2
이 예에서 중요한 문제는 다음과 같습니다. 누가 연결을 종료해야합니까? 이 연결을 초기 값으로 작성하는 대신 연결 소비자가 연결을 명시 적으로 작성하고 ThreadLocal을 통해 스레드에 바인딩하도록하십시오. 그런 다음 연결 작성자도 닫을 책임이 있습니다. 이 예에서는 명확하지 않습니다. Transaction.begin () 및 Transaction.end ()와 같은 간단한 프레임 워크에서 연결 작성 및 바인딩을 숨길 수도 있습니다.
Rick-Rainer Ludwig

2

Java 의 ThreadLocal 클래스를 사용하면 동일한 스레드에서만 읽고 쓸 수있는 변수를 작성할 수 있습니다. 따라서 두 스레드가 동일한 코드를 실행하고 코드에 ThreadLocal 변수에 대한 참조가 있어도 두 스레드는 서로의 ThreadLocal 변수를 볼 수 없습니다.

더 읽어보기


2

사용하기위한 3 가지 시나리오가 있습니다 클래스 헬퍼를 최고의 하나는 사용하는 것입니다 멀티 스레드 코드에서 SimpleDateFormat에, 같이가 의 ThreadLocal

시나리오

1 같이 사용하여 공유 객체 의 도움으로 잠금 또는 동기화 앱을 만드는 메커니즘 느린

2- 메소드 내에서 로컬 객체 로 사용

우리가이 시나리오에서, 4 스레드를 각각 하나의 방법 1000 호출 후 우리는 한 시간에
4000 SimpleDateFormat의의 객체 생성 및 GC 그들을 삭제 대기를

3- ThreadLocal 사용

우리는 4 스레드를 가지고 있다면 우리는 준 각 스레드 하나의 SimpleDateFormat의 인스턴스
우리는 그래서 4 개 스레드 , 4 객체 SimpleDateFormat에의합니다.

잠금 메커니즘과 객체 생성 및 소멸이 필요하지 않습니다. (좋은 시간 복잡성과 공간 복잡성)


2

[참고] ThreadLocal은 공유 객체의 업데이트 문제를 해결할 수 없습니다. 동일한 스레드에서 모든 조작이 공유하는 staticThreadLocal 오브젝트를 사용하는 것이 좋습니다. [필수] remove () 메소드는 ThreadLocal 변수, 특히 스레드가 자주 재사용되는 스레드 풀을 사용할 때 구현되어야합니다. 그렇지 않으면 후속 비즈니스 로직에 영향을 미치고 메모리 누수와 같은 예기치 않은 문제가 발생할 수 있습니다.


1

캐싱, 때로는 같은 값을 많은 시간을 계산해야하므로 마지막 입력 세트를 메소드에 저장하면 코드 속도를 높일 수 있습니다. Thread Local Storage를 사용하면 잠금에 대해 생각할 필요가 없습니다.


1

ThreadLocal은 스레드에 대해서만 격리 된 스토리지 공간을 제공하기 위해 JVM에서 특별히 프로비저닝 된 기능입니다. 인스턴스 범위 변수의 값과 같이 클래스의 주어진 인스턴스에만 바인딩됩니다. 각 객체에는 유일한 값이 있으며 서로 다른 값을 볼 수 없습니다. ThreadLocal 변수의 개념도 마찬가지입니다. 그것들은 그것을 생성 한 것을 제외한 다른 스레드의 객체 인스턴스의 의미에서 스레드에 국한되어 있습니다. 여기를 봐

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}

1

Threadlocal은 비용없이 객체 재사용 성을 달성하는 매우 쉬운 방법을 제공합니다.

여러 업데이트 스레드에서 여러 스레드가 변경 가능한 캐시 이미지를 생성하는 상황이있었습니다.

각 스레드에서 Threadlocal을 사용한 다음 각 스레드는 이전 이미지를 재설정 한 다음 각 업데이트 알림의 캐시에서 다시 업데이트해야합니다.

객체 풀에서 재사용 할 수있는 일반적인 객체는 스레드 안전성 비용과 관련이 있지만이 방법에는 아무 것도 없습니다.


0

ThreadLocal 변수에 대한 느낌을 얻으려면이 작은 예제를 시도하십시오.

public class Book implements Runnable {
    private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);

    private final String bookName; // It is also the thread's name
    private final List<String> words;


    public Book(String bookName, List<String> words) {
        this.bookName = bookName;
        this.words = Collections.unmodifiableList(words);
    }

    public void run() {
        WORDS.get().addAll(words);
        System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
        Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
        t1.start();
        t2.start();
    }
}


스레드 BookA가 먼저 수행되는 경우 콘솔 출력 :
결과 BookA : 'wordA1, wordA2, wordA3'.
결과 BookB : 'wordB1, wordB2'.

스레드 BookB가 먼저 수행되는 경우 콘솔 출력 :
결과 BookB : 'wordB1, wordB2'.
결과 북 A : 'wordA1, wordA2, wordA3'.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.