Kiedy i jak powinienem używać zmiennej ThreadLocal?


876

Kiedy powinienem użyć ThreadLocalzmiennej?

Jak to jest używane?


11
Jeśli używasz ThreadLocal, nigdy nie pisz na nim opakowania !!! Każdy programista, który chce korzystać ze zmiennej, musi wiedzieć, że jest to „ThreadLocal”
nie jest to błąd

2
@Notabug Jeśli wyrażasz się jasno, możesz nazwać swoją zmienną tak, aby to ona zajmowała się wartością ThreadLocal, np RequestUtils.getCurrentRequestThreadLocal(). Nie oznacza to jednak, że jest to bardzo eleganckie, ale wynika to z faktu, że sam ThreadLocal w większości przypadków nie jest zbyt elegancki.
Whirlwin

@Sasha Can ThreadLocal może być używany do przechowywania śladu wykonania
gaurav

Odpowiedzi:


864

Jednym z możliwych (i powszechnych) zastosowań jest posiadanie obiektu, który nie jest bezpieczny dla wątków, ale chcesz uniknąć synchronizowania dostępu do tego obiektu (patrzę na ciebie, SimpleDateFormat ). Zamiast tego nadaj każdemu wątkowi własną instancję obiektu.

Na przykład:

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);
    }
}

Dokumentacja .


160
Inną alternatywą dla synchronizacji lub Threadlocal jest uczynienie zmiennej zmienną lokalną. Lokalne zmienne są zawsze bezpieczne dla wątków. Zakładam, że złym rozwiązaniem jest tworzenie lokalnych formatów DateFormats, ponieważ ich tworzenie jest kosztowne, ale nigdy nie widziałem solidnych danych na ten temat.
Julien Chastang

18
To wysoka cena za włamanie SimpleDateFormat. Być może lepiej jest użyć bezpiecznej dla wątków alternatywy . Jeśli zgadzasz się, że singletons są złe potem ThreadLocaljest jeszcze gorzej.
Alexander Ryzhov

4
@ overthink Czy istnieje powód, aby zadeklarować statyczny i końcowy ThreadLocal, mam na myśli wydajność lub coś takiego?
Sachin Gorade,

4
Metoda ThreadLocal.get () wywoła ThreadLocal.initialValue () (jeden raz) dla każdego wątku, co oznacza, że ​​dla każdego wątku tworzony jest obiekt SimpleDateFormat. Czy nie lepiej jest po prostu mieć SimpleDateFormat jako zmienne lokalne (ponieważ nie mamy do czynienia z problemami związanymi z odśmiecaniem)?
sudeepdino008

3
Uważaj tylko, aby nie używać inicjalizacji podwójnego nawiasu klamrowego dla Twojego SimpleDateFormat, ponieważ stworzyłoby to anonimową klasę, dzięki czemu moduł ładujący klasy nie mógłby zostać wyrzucony. wyciek pamięci: zwraca nowy SimpleDateFormat () {{ApplyPattern ("rrrrMMdd GGmm")}};
user1944408 27.07.16

427

Ponieważ a ThreadLocaljest odniesieniem do danych w danym obszarze Thread, możesz skończyć przeciekami klasy podczas używania ThreadLocals na serwerach aplikacji za pomocą pul wątków. Trzeba być bardzo ostrożnym czyszczenia żadnych ThreadLocaljesteś ty get()lub set()za pomocą ThreadLocal„s remove()metody.

Jeśli nie wyczyścisz po zakończeniu, wszelkie odwołania do klas załadowanych jako część wdrożonej aplikacji internetowej pozostaną na stałym stosie i nigdy nie zostaną usunięte. Ponowne wdrożenie / cofnięcie aplikacji internetowej nie wyczyści Threadodniesienia do klas aplikacji, ponieważ Threadnie jest to własność aplikacji internetowej. Każde kolejne wdrożenie spowoduje utworzenie nowej instancji klasy, która nigdy nie będzie zbierana w pamięci.

java.lang.OutOfMemoryError: PermGen spaceSkończysz z brakami pamięci z powodu i po pewnym googlingu prawdopodobnie po prostu wzrośnie -XX:MaxPermSizezamiast naprawiać błąd.

Jeśli napotkasz te problemy, możesz ustalić, który wątek i klasa zachowuje te referencje, korzystając z narzędzia Eclipse's Memory Analyzer i / lub postępując zgodnie z instrukcjami Franka Kieviet i działaniami następczymi .

Aktualizacja: Ponownie odkryłem wpis na blogu Alexa Vasseura, który pomógł mi wyśledzić niektóre ThreadLocalproblemy, które miałem.


11
Alex Vasseur przeniósł swojego bloga. Oto aktualny link do artykułu o wycieku pamięci.
Kenster

12
Wydaje się, że wątek, do którego prowadzi Julien, został tutaj przeniesiony i warto go przeczytać…
Donal Fellows,

28
Jest to okropne poparcie dla odpowiedzi, która, choć pouczająca, w rzeczywistości w żaden sposób nie odpowiada na pytanie.
Robin

8
Teraz, gdy PermGen został zabity przez Javę 8, czy to w jakikolwiek sposób zmienia tę odpowiedź?
Zła pralka

13
@Robin: Nie zgadzam się. Pytanie dotyczy tego, jak prawidłowo używać ThreadLocal, aby w pełni zrozumieć dowolną koncepcję (tutaj ThreadLocal), ważne jest również, aby zrozumieć, jak jej nie używać i ryzykować niestosownym użyciem, i na tym właśnie polega odpowiedź Phila. Cieszę się, że opisał tę kwestię, która nie została ujęta w żadnej innej odpowiedzi. Wszystkie te głosy są zasłużone. Uważam, że SO powinno budować zrozumienie pojęć, a nie tylko być stroną zapewniania jakości.
Saurabh Patil

166

Wiele frameworków używa ThreadLocals do utrzymywania kontekstu związanego z bieżącym wątkiem. Na przykład, gdy bieżąca transakcja jest przechowywana w ThreadLocal, nie musisz przekazywać jej jako parametru przy każdym wywołaniu metody, na wypadek, gdyby ktoś na stosie potrzebował do niej dostępu. Aplikacje internetowe mogą przechowywać informacje o bieżącym żądaniu i sesji w ThreadLocal, aby aplikacja miała do nich łatwy dostęp. Dzięki Guice możesz używać ThreadLocals podczas implementowania niestandardowych zakresów dla wstrzykiwanych obiektów (domyślnie zakresy serwletów Guice najprawdopodobniej również z nich korzystają).

ThreadLocals to jeden z rodzajów zmiennych globalnych (choć nieco mniej złych, ponieważ są ograniczone do jednego wątku), dlatego należy zachować ostrożność podczas ich używania, aby uniknąć niepożądanych efektów ubocznych i wycieków pamięci. Zaprojektuj swoje interfejsy API, aby wartości ThreadLocal były zawsze automatycznie usuwane, gdy nie są już potrzebne, i że nieprawidłowe użycie interfejsu API nie będzie możliwe (na przykład w ten sposób ). ThreadLocals może być użyty do czyszczenia kodu, a w niektórych rzadkich przypadkach są one jedynym sposobem, aby coś zadziałało (mój obecny projekt miał dwa takie przypadki; są one udokumentowane tutaj w „Static Fields and Global Variables”).


4
Właśnie w ten sposób środowisko exPOJO (www.expojo.com) umożliwia dostęp do ORM Session / PersistenceManager bez potrzeby narzucania adnotacji i zastrzyków. Jest to coś w rodzaju „wstrzyknięcia nici” zamiast „wstrzyknięcia obiektu”. Zapewnia dostęp do zależności bez konieczności (i narzutu) osadzania ich w każdym obiekcie, który może potrzebować tych zależności. To niesamowite, jak „lekka” może być struktura szkieletowa DI, gdy stosuje się wstrzyknięcie gwintu zamiast klasycznej DI (np. Wiosna itp.)
Volksman

27
Dlaczego musiałem przewijać do tej pory, aby znaleźć tę istotną odpowiedź !?
Jeremy Stein

1
@Esko, W twoim projekcie użycie ThreadLocal w hashcode i equals jest niepotrzebne. Lepszym rozwiązaniem jest utworzenie lub przekazanie odwołania do funkcji hashcode / equals, ilekroć jest to potrzebne. W ten sposób możesz całkowicie uniknąć konieczności hackowania ThreadLocal.
Pacerier


1
To najlepsze wytłumaczenie użycia TL.
Dexter,

52

W Javie, jeśli masz układ odniesienia, który może różnić się w zależności od wątku, możesz przekazać ten układ do każdej metody, która go potrzebuje (lub może potrzebować), lub powiązać układ z wątkiem. Przekazywanie punktu odniesienia wszędzie może być wykonalne, jeśli wszystkie twoje metody muszą już przekazywać wspólną zmienną „kontekstową”.

Jeśli tak nie jest, możesz nie chcieć zaśmiecać podpisów metod dodatkowym parametrem. W świecie bez wątków można rozwiązać problem z odpowiednikiem zmiennej globalnej w języku Java. W słowie wątkowym odpowiednikiem zmiennej globalnej jest zmienna lokalna.


13
Dlatego powinieneś unikać miejscowych wątków w taki sam sposób, jak unika się globalizacji. Nie mogę zaakceptować, że tworzenie globałów (miejscowych wątków) jest OK, zamiast przekazywać wartości, ludzie nie lubią, ponieważ często ujawniają problemy architektury, których nie chcą naprawiać.
Juan Mendes,

10
Być może ... Może to być przydatne, jeśli masz ogromną, istniejącą bazę kodów, do której musisz dodać nową bazę danych, która musi być przekazywana wszędzie , np. Kontekst sesji, transakcja db, zalogowany użytkownik itp. .
Cornel Masson

6
Wyrazy uznania za korzystanie z danych zamiast danych.
głęboki

To mnie zaciekawiło. 'datum' is the singular form and 'data' is the plural form.
Siddhartha

23

Jest bardzo dobry przykład w książce Java Concurrency in Practice . Tam, gdzie autor ( Joshua Bloch ) wyjaśnia, w jaki sposób ograniczanie wątków jest jednym z najprostszych sposobów na osiągnięcie bezpieczeństwa wątków, a ThreadLocal jest bardziej formalnym sposobem utrzymywania ograniczania wątków. Na koniec wyjaśnia również, w jaki sposób ludzie mogą go nadużywać, używając go jako zmiennych globalnych.

Skopiowałem tekst ze wspomnianej książki, ale brakuje kodu 3.10, ponieważ nie jest ważne, aby zrozumieć, gdzie powinien być używany ThreadLocal.

Zmienne lokalne wątków są często używane, aby zapobiec współużytkowaniu w projektach opartych na zmiennych Singletonach lub zmiennych globalnych. Na przykład aplikacja jednowątkowa może utrzymywać globalne połączenie z bazą danych inicjowane podczas uruchamiania, aby uniknąć konieczności przekazywania połączenia do każdej metody. Ponieważ połączenia JDBC mogą nie być bezpieczne dla wątków, aplikacja wielowątkowa korzystająca z połączenia globalnego bez dodatkowej koordynacji również nie jest bezpieczna dla wątków. Używając ThreadLocal do przechowywania połączenia JDBC, tak jak w ConnectionHolder na Listingu 3.10, każdy wątek będzie miał własne połączenie.

ThreadLocal jest szeroko stosowany we wdrażaniu struktur aplikacji. Na przykład kontenery J2EE kojarzą kontekst transakcji z wątkiem wykonawczym na czas trwania wywołania EJB. Można to łatwo zaimplementować za pomocą statycznego Lokalnego wątku przechowującego kontekst transakcji: gdy kod frameworka musi określić, która transakcja jest aktualnie wykonywana, pobiera kontekst transakcji z tego obiektu ThreadLocal. Jest to wygodne, ponieważ zmniejsza potrzebę przekazywania informacji kontekstu wykonania do każdej metody, ale łączy dowolny kod korzystający z tego mechanizmu ze strukturą.

Łatwo jest nadużywać ThreadLocal, traktując jego właściwość ograniczania wątków jako licencję na używanie zmiennych globalnych lub jako środek do tworzenia „ukrytych” argumentów metod. Podobnie jak zmienne globalne, zmienne lokalne wątków mogą niekorzystnie wpływać na możliwość ponownego użycia i wprowadzać ukryte sprzężenia między klasami, dlatego też należy ich używać ostrożnie.


18

Zasadniczo, gdy potrzebujesz wartości zmiennej zależnej od bieżącego wątku i nie jest wygodne dołączanie wartości do wątku w inny sposób (na przykład wątek podklasy).

Typowy przypadek ma miejsce, gdy jakiś inny framework utworzył wątek, w którym działa twój kod, np. Kontener serwletu, lub gdy bardziej sensowne jest użycie ThreadLocal, ponieważ twoja zmienna jest wtedy „w swoim logicznym miejscu” (zamiast zmiennej zwisające z podklasy Wątek lub na innej mapie mieszającej).

Na mojej stronie internetowej omówiłem kilka przykładów i przykładów użycia ThreadLocal, które mogą być również interesujące.

Niektóre osoby opowiadają się za użyciem ThreadLocal jako sposobu na dołączenie „ID wątku” do każdego wątku w pewnych współbieżnych algorytmach, w których potrzebny jest numer wątku (patrz np. Herlihy i Shavit). W takich przypadkach sprawdź, czy naprawdę dostajesz zasiłek!


Czy można użyć ThreadLocal do przechowywania śladu wykonania
gaurav

13
  1. ThreadLocal w Javie został wprowadzony w JDK 1.2, ale później został wygenerowany w JDK 1.5 w celu wprowadzenia bezpieczeństwa typu w zmiennej ThreadLocal.

  2. ThreadLocal może być powiązany z zakresem wątku, cały kod wykonywany przez Thread ma dostęp do zmiennych ThreadLocal, ale dwa wątki nie widzą się nawzajem Zmienna ThreadLocal.

  3. Każdy wątek zawiera wyłączną kopię zmiennej ThreadLocal, która kwalifikuje się do wyrzucania elementów bezużytecznych po zakończeniu lub śmierci wątku, normalnie lub z powodu wyjątku, biorąc pod uwagę, że zmienna ThreadLocal nie ma żadnych innych aktywnych odniesień.

  4. Zmienne ThreadLocal w Javie są zwykle prywatnymi polami statycznymi w klasach i utrzymują swój stan w wątku.

Czytaj więcej: ThreadLocal w Javie - Przykładowy program i samouczek


Czy można użyć ThreadLocal do przechowywania śladu wykonania
gaurav

11

Dokumentacja mówi bardzo dobrze: „każdy wątek, który uzyskuje dostęp do [zmiennej lokalnej wątku] (za pomocą metody get lub set) ma swoją własną, niezależnie inicjowaną kopię zmiennej”.

Używasz jednego, gdy każdy wątek musi mieć własną kopię czegoś. Domyślnie dane są dzielone między wątkami.


18
Domyślnie obiekty statyczne lub obiekty jawnie przekazywane między wątkami, w których oba wątki zawierają odwołanie do tego samego obiektu, są współużytkowane. Obiekty zadeklarowane lokalnie w wątku nie są udostępniane (są lokalne dla stosu wątku). Chciałem tylko to wyjaśnić.
Ian Varley

@IanVarley: Dzięki za wskazanie tego. Więc jeśli mamy zmienną zadeklarowaną i używaną w klasie MyThread, to czy potrzebujemy ThreadLocal w tym przypadku? Przykład: klasa MyThread rozszerza Wątek {Ciąg var1 ;, int var2; } w tym przypadku var1 i var2 będą częścią stosu wątków i nie będą udostępniane, więc czy w tym przypadku potrzebujemy ThreadLocal? Mogę całkowicie się mylić, proszę, prowadź.
Jayesh

4
Jeśli każdy wątek chce mieć własną kopię czegoś, dlaczego nie można go po prostu zadeklarować jako lokalny (co zawsze jest bezpieczne dla wątku)?
sudeepdino008

@ sudeepdino008 nie zawsze masz kontrolę nad tworzeniem tych wątków (frameworki, biblioteki wątków)
JMess 24.04.18

10

Serwer Webapp może przechowywać pulę wątków, a ThreadLocalvar powinien zostać usunięty przed odpowiedzią na klienta, dlatego bieżący wątek może zostać ponownie wykorzystany przy następnym żądaniu.


8

Dwa przypadki użycia, w których można zastosować zmienną Threadlocal -
1- Gdy mamy wymaganie skojarzenia stanu z wątkiem (np. Identyfikator użytkownika lub identyfikator transakcji). Zdarza się to zwykle w przypadku aplikacji internetowej, z którą każde żądanie kierowane do serwletu ma przypisany unikalny identyfikator transakcji.

// 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();
    }
}

Zauważ, że tutaj metoda withInitial jest implementowana przy użyciu wyrażenia lambda.
2- Innym przypadkiem użycia jest sytuacja, gdy chcemy mieć instancję bezpieczną dla wątków i nie chcemy używać synchronizacji, ponieważ koszt wydajności z synchronizacją jest większy. Jednym z takich przypadków jest użycie SimpleDateFormat. Ponieważ SimpleDateFormat nie jest bezpieczny dla wątków, musimy zapewnić mechanizm, aby był bezpieczny dla wątków.

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()));
    } 

}

Nadal nie widziałem korzyści z używania ThreadLocal do wygenerowania unikalnego identyfikatora w przykładzie 1, jeśli Twoim jedynym celem jest zwrócenie przyrostowej unikalnej wartości całkowitej. `` public class ThreadId {// Atomowa liczba całkowita zawierająca następny identyfikator wątku, który ma zostać przypisany prywatnej statycznej końcowej AtomicInteger nextId = new AtomicInteger (0); // Zwraca unikalny identyfikator bieżącego wątku, przypisując go w razie potrzeby public static int get () {return nextId..getAndIncrement (); }} ``
dashenswen

7

Od wydania Java 8 istnieje bardziej deklaratywny sposób inicjowania ThreadLocal:

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

Do czasu wydania Java 8 trzeba było wykonać następujące czynności:

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

Ponadto, jeśli metoda instancji (konstruktor, metoda fabryczna) klasy, dla której jest używana ThreadLocal, nie przyjmuje żadnych parametrów, możesz po prostu użyć odwołań do metod (wprowadzonych w Javie 8):

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

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

Uwaga: Ocena jest leniwa, ponieważ przekazujesz java.util.function.Supplierlambda, która jest oceniana tylko wtedy, gdy ThreadLocal#getjest wywoływana, ale wartość nie była wcześniej oceniana.


5

Musisz być bardzo ostrożny ze wzorem ThreadLocal. Istnieje kilka poważnych wad, takich jak wspomniany Phil, ale jednym z nich nie było to, aby upewnić się, że kod, który konfiguruje kontekst ThreadLocal, nie jest „ponownie dostępny”.

Złe rzeczy mogą się zdarzyć, gdy kod ustawiający informacje zostanie uruchomiony drugi lub trzeci raz, ponieważ informacje w twoim wątku mogą zacząć mutować, gdy się tego nie spodziewałeś. Dlatego upewnij się, że informacje ThreadLocal nie zostały ustawione przed ponownym ustawieniem.


2
Ponowne wejście nie stanowi problemu, jeśli kod jest przygotowany na to. Przy wejściu zanotuj, czy zmienna jest już ustawiona, a przy wyjściu przywróć jej poprzednią wartość (jeśli była) lub usuń ją (jeśli nie).
supercat

1
@Jeff, stary, dotyczy to każdego kodu, który piszesz, nie tylko ThreadLocalwzorca. Jeśli to zrobisz, F(){ member=random(); F2(); write(member); }a F2 zastąpi członka nową wartością, wtedy oczywiście write(member)nie zapisze już edytowanej liczby random(). To dosłownie zdrowy rozsądek. Podobnie, jeśli tak F(){ F(); }, to gd powodzenia z nieskończoną pętlą! Jest to prawdą wszędzie i nie jest specyficzne ThreadLocal.
Pacerier

@Jeff, przykład kodu?
Pacerier

5

kiedy?

Jeśli obiekt nie jest bezpieczny dla wątków, zamiast synchronizacji, która utrudnia skalowalność, należy dać jeden obiekt każdemu wątkowi i zachować zakres wątku, którym jest ThreadLocal. Jednym z najczęściej używanych, ale niechronionych wątków obiektów, jest połączenie z bazą danych i JMSConnection.

W jaki sposób ?

Jednym z przykładów jest platforma Spring, która mocno wykorzystuje ThreadLocal do zarządzania transakcjami za kulisami, utrzymując te obiekty połączeń w zmiennych ThreadLocal. Na wysokim poziomie, po rozpoczęciu transakcji, otrzymuje połączenie (i wyłącza automatyczne zatwierdzanie) i utrzymuje je w ThreadLocal. przy kolejnych wywołaniach db używa tego samego połączenia do komunikacji z db. Na koniec pobiera połączenie z ThreadLocal i zatwierdza (lub przywraca) transakcję i zwalnia połączenie.

Myślę, że log4j używa także ThreadLocal do obsługi MDC.


5

ThreadLocal jest przydatny, gdy chcesz mieć stan, który nie powinien być współużytkowany przez różne wątki, ale powinien być dostępny z każdego wątku przez cały okres jego użytkowania.

Jako przykład wyobraź sobie aplikację internetową, w której każde żądanie jest obsługiwane przez inny wątek. Wyobraź sobie, że do każdego żądania potrzebujesz fragmentu danych wiele razy, co jest dość drogie do obliczenia. Jednak te dane mogły ulec zmianie dla każdego przychodzącego żądania, co oznacza, że ​​nie można użyć zwykłej pamięci podręcznej. Prostym i szybkim rozwiązaniem tego problemu byłoby posiadanie ThreadLocalzmiennej przechowującej dostęp do tych danych, dzięki czemu trzeba ją obliczyć tylko raz dla każdego żądania. Oczywiście ten problem można również rozwiązać bez użycia ThreadLocal, ale opracowałem go w celach ilustracyjnych.

To powiedziawszy, pamiętajcie, że ThreadLocalsą one zasadniczo formą państwa globalnego. W rezultacie ma wiele innych implikacji i należy go stosować dopiero po rozważeniu wszystkich innych możliwych rozwiązań.


ThreadLocals NIE są stanem globalnym, chyba że zostanie ustawiony na stan globalny. Są to praktycznie zawsze dostępne zasoby stosu. Możesz symulować wątek lokalnie, po prostu przekazując tę ​​zmienną we wszystkich swoich metodach (co jest opóźnione); to nie czyni go globalnym stanem ...
Enerccio,

Jest to forma stanu globalnego w tym sensie, że jest dostępna z dowolnego miejsca w kodzie (w kontekście tego samego wątku). Jest to związane ze wszystkimi konsekwencjami, takimi jak brak możliwości uzasadnienia, kto czyta i zapisuje tę wartość. Używanie parametru funkcji nie jest opóźnione, jest to dobra praktyka promująca czyste interfejsy. Zgadzam się jednak z tobą, że przekazanie parametru na całej głębokości bazy kodu jest zapachem kodu. Ale wierzę również, że w wielu przypadkach użycie ThreadLocal jest przede wszystkim zapachem kodu, który cię tu zaprowadził, więc należy to ponownie rozważyć.
Dimos,

Może być po prostu częścią stanu obiektu, nie musi być używany globalnie. Jasne, masz trochę narzutów, ale jeśli wiele obiektów musi mieć inny stan na wątek, możesz użyćThreadLocal jako pola obiektu ...
Enerccio,

Masz rację. Prawdopodobnie natknąłem się na kilka niewłaściwych zastosowań ThreadLocal, gdzie był on dostępny na całym świecie. Jak powiedziałeś, nadal może być używany jako pole klasy ograniczające widoczność.
Dimos,

4

Nie ma tu nic nowego, ale dzisiaj odkryłem, że ThreadLocaljest to bardzo przydatne, gdy korzystam z Bean Validation w aplikacji internetowej. Komunikaty sprawdzania poprawności są zlokalizowane, ale domyślnie są używane Locale.getDefault(). Możesz skonfigurować Validatorinny MessageInterpolator, ale nie ma sposobu, aby określić, Localekiedy zadzwoniszvalidate . Możesz więc stworzyć statyczny ThreadLocal<Locale>(lub jeszcze lepiej, ogólny kontener z innymi rzeczami, które mogą być potrzebne, ThreadLocala następnie MessageInterpolatorwybrać niestandardowy Localez tego. Kolejnym krokiem jest napisanie, ServletFilterktóry używa wartości sesji lub request.getLocale()wybór ustawień regionalnych i przechowywania to w twojej ThreadLocalreferencji.


4

Jak wspomniano w @unknown (google), jego użycie polega na zdefiniowaniu zmiennej globalnej, w której wartość odniesienia może być unikalna w każdym wątku. Jego użycie zwykle oznacza przechowywanie pewnego rodzaju informacji kontekstowych, które są powiązane z bieżącym wątkiem wykonania.

Używamy go w środowisku Java EE, aby przekazać tożsamość użytkownika do klas, które nie są świadome Java EE (nie mają dostępu do HttpSession lub EJB SessionContext). W ten sposób kod, który wykorzystuje tożsamość do operacji opartych na bezpieczeństwie, może uzyskać dostęp do tożsamości z dowolnego miejsca, bez konieczności jawnego przekazywania jej przy każdym wywołaniu metody.

Cykl operacji żądanie / odpowiedź w większości wywołań Java EE ułatwia ten rodzaj użytkowania, ponieważ zapewnia dobrze zdefiniowane punkty wejścia i wyjścia do ustawiania i wyłączania ThreadLocal.


4

ThreadLocal zapewni dostęp do obiektu zmiennego przez wiele wątków w niezsynchronizowanej metodzie jest zsynchronizowany, co oznacza, że ​​obiekt zmienny będzie niezmienny w obrębie metody.

Osiąga się to poprzez podanie nowej instancji obiektu zmiennego dla każdej próby uzyskania dostępu do wątku. Jest to lokalna kopia dla każdego wątku. Jest to pewien hack przy tworzeniu zmiennej instancji w metodzie dostępnej jak zmienna lokalna. Ponieważ wiesz, że zmienna lokalna metody jest dostępna tylko dla wątku, jedną różnicą jest; zmienne lokalne metody nie będą dostępne dla wątku po zakończeniu wykonywania metody, w której obiekt zmienny współdzielony z Threadlocal będzie dostępny dla wielu metod, dopóki go nie wyczyścimy.

Zgodnie z definicją:

Klasa ThreadLocal w Javie umożliwia tworzenie zmiennych, które mogą być odczytywane i zapisywane tylko przez ten sam wątek. Dlatego nawet jeśli dwa wątki wykonują ten sam kod, a kod zawiera odwołanie do zmiennej ThreadLocal, wówczas oba wątki nie widzą nawzajem swoich zmiennych ThreadLocal.

Każda Threadw java zawiera ThreadLocalMapw sobie.
Gdzie

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

Osiągnięcie ThreadLocal:

Teraz stwórz klasę opakowania dla ThreadLocal, która będzie przechowywać zmienny obiekt jak poniżej (z lub bez initialValue()).
Teraz getter i setter tego opakowania będą działały na instancji Threadlocal zamiast obiektu mutable.

Jeśli getter () z Threadlocal nie znalazł żadnej wartości z w Threadlocalmap na Thread; następnie wywoła funkcję initialValue (), aby uzyskać swoją prywatną kopię dotyczącą wątku.

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();
    }
}

Teraz wrapper.getDateFormatter()zadzwoni threadlocal.get()i sprawdzi, czy currentThread.threadLocalMapzawiera to wystąpienie (Threadlocal).
Jeśli tak, zwróć wartość (SimpleDateFormat) dla odpowiedniej
instancji Threadlocal, w przeciwnym razie dodaj mapę z tą instancją ThreadLocal, initialValue ().

W ten sposób osiągnięto bezpieczeństwo wątków w tej klasie zmiennych; przez każdy wątek działa z własną instancją mutable, ale z tą samą instancją ThreadLocal. Oznacza, że ​​cały wątek będzie współdzielił tę samą instancję ThreadLocal jako klucz, ale inną instancję SimpleDateFormat jako wartość.

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


3

Zmienne lokalne wątków są często używane, aby zapobiec współużytkowaniu w projektach opartych na zmiennych Singletonach lub zmiennych globalnych.

Może być używany w scenariuszach takich jak nawiązywanie oddzielnego połączenia JDBC dla każdego wątku, gdy nie używasz puli połączeń.

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

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

Wywołanie getConnection spowoduje zwrócenie połączenia powiązanego z tym wątkiem. To samo można zrobić z innymi właściwościami, takimi jak dateformat, kontekst transakcji, którego nie chcesz udostępniać między wątkami.

Mogłeś również użyć zmiennych lokalnych do tego samego, ale te zasoby zwykle zajmują dużo czasu podczas tworzenia, więc nie chcesz ich tworzyć ponownie za każdym razem, gdy wykonujesz z nimi logikę biznesową. Jednak wartości ThreadLocal są przechowywane w samym obiekcie wątku i gdy tylko wątek zostanie wyrzucony, elementy te również znikają.

Ten link bardzo dobrze wyjaśnia użycie ThreadLocal.


2
W tym przykładzie jest poważny problem: kto jest odpowiedzialny za zamknięcie połączenia? Zamiast tworzyć to połączenie jako wartość initiali, pozwól konsumentowi połączenia jawnie utworzyć połączenie i powiązać je z wątkiem za pośrednictwem ThreadLocal. Twórca połączenia jest wówczas również odpowiedzialny za zamknięcie. W tym przykładzie nie jest to jasne. Tworzenie i wiązanie połączenia można również ukryć w prostym frameworku za pomocą Transaction.begin () i Transaction.end ().
Rick-Rainer Ludwig

2

ThreadLocal klasa w Java umożliwia tworzenie zmiennych, które mogą być odczytywane i zapisywane przez tego samego wątku tylko. Dlatego nawet jeśli dwa wątki wykonują ten sam kod, a kod zawiera odwołanie do zmiennej ThreadLocal, wówczas oba wątki nie widzą nawzajem swoich zmiennych ThreadLocal.

Czytaj więcej


2

Istnieją 3 scenariusze korzystania z pomocnika klasy, takiego jak SimpleDateFormat w kodzie wielowątkowym, z których najlepszym jest użycie ThreadLocal

Scenariusze

1- Korzystanie z podobnego obiektu udostępniania za pomocą mechanizmu blokady lub synchronizacji , który spowalnia działanie aplikacji

2- Używanie jako obiektu lokalnego w metodzie

W tym scenariuszu, jeśli mamy 4 wątki, każdy wywołuje metodę 1000 razy, to mamy
4000 obiektów SimpleDateFormat utworzonych i oczekujących na ich usunięcie przez GC

3- Korzystanie z ThreadLocal

jeśli mamy 4 wątki i podaliśmy każdemu wątkowi jedną instancję SimpleDateFormat,
więc mamy 4 wątki , 4 obiekty SimpleDateFormat.

Nie ma potrzeby blokowania mechanizmu oraz tworzenia i niszczenia obiektów. (Dobra złożoność czasu i złożoność przestrzeni)


2

[Dla odniesienia] ThreadLocal nie może rozwiązać problemów z aktualizacją współdzielonego obiektu. Zalecane jest użycie obiektu staticThreadLocal, który jest wspólny dla wszystkich operacji w tym samym wątku. [Obowiązkowe] metoda remove () musi być zaimplementowana przez zmienne ThreadLocal, szczególnie przy użyciu pul wątków, w których wątki są często ponownie wykorzystywane. W przeciwnym razie może to wpłynąć na późniejszą logikę biznesową i spowodować nieoczekiwane problemy, takie jak wyciek pamięci.


1

Buforowanie, czasami musisz obliczyć tę samą wartość dużo czasu, więc przechowując ostatni zestaw danych wejściowych w metodzie, a wynik możesz przyspieszyć kod. Używając Thread Local Storage, nie musisz myśleć o blokowaniu.


1

ThreadLocal jest specjalnie wyposażoną funkcją JVM, która zapewnia izolowane miejsce do przechowywania tylko dla wątków. podobnie jak wartość zmiennej o zasięgu instancji są powiązane tylko z danym wystąpieniem klasy. każdy obiekt ma swoje jedyne wartości i nie mogą widzieć siebie nawzajem. podobnie jest koncepcja zmiennych ThreadLocal, są one lokalne dla wątku w sensie instancji obiektu inny wątek, z wyjątkiem tego, który go utworzył, nie może go zobaczyć. Spójrz tutaj

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 zapewnia bardzo łatwy sposób na ponowne użycie obiektów przy zerowym koszcie.

Miałem sytuację, w której wiele wątków tworzyło obraz modyfikowalnej pamięci podręcznej przy każdym powiadomieniu o aktualizacji.

Użyłem Threadlocal dla każdego wątku, a następnie każdy wątek musiałby po prostu zresetować stary obraz, a następnie zaktualizować go ponownie z pamięci podręcznej przy każdym powiadomieniu o aktualizacji.

Zwykłe obiekty wielokrotnego użytku z pul obiektów są powiązane z kosztami bezpieczeństwa wątków, podczas gdy takie podejście nie ma żadnego.


0

Wypróbuj ten mały przykład, aby poznać zmienną 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();
    }
}


Dane wyjściowe konsoli, jeśli wątek BookA zostanie wykonany jako pierwszy:
Wynik BookA: „słowo A1, słowo A2, słowo A3”.
Wynik BookB: „słowo B1, słowo B2”.

Dane wyjściowe konsoli, jeśli wątek BookB zostanie zrobiony jako pierwszy:
Wynik BookB: 'wordB1, wordB2'.
Księga wyników A: „słowo A1, słowo A2, słowo A3”.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.