Dlaczego zmienne Java ThreadLocal powinny być statyczne


102

Czytałem tutaj JavaDoc dla Threadlocal

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

i mówi, że „wystąpienia ThreadLocal są zwykle prywatnymi polami statycznymi w klasach, które chcą powiązać stan z wątkiem (np. identyfikator użytkownika lub identyfikator transakcji)”.

Ale moje pytanie brzmi: dlaczego zdecydowali się uczynić go statycznym (zazwyczaj) - może to trochę zagmatwać stan „na wątek”, ale pola są statyczne?

Odpowiedzi:


132

Ponieważ gdyby było to pole na poziomie instancji, w rzeczywistości byłoby to „Na wątek - na wystąpienie”, a nie tylko gwarantowane „Na wątek”. Zwykle nie jest to semantyka, której szukasz.

Zwykle zawiera coś podobnego do obiektów, które są objęte zakresem konwersacji użytkownika, żądania sieci Web itp. Nie chcesz, aby były one również objęte zakresem instancji klasy.
Jedno żądanie sieciowe => jedna sesja trwałości.
Żadne żądanie sieciowe => jedna sesja utrwalania na obiekt.


2
Podoba mi się to wyjaśnienie, ponieważ pokazuje, jak używać ThreadLocal
kellyfj

4
Per-thread-per-instance może być użyteczną semantyczną, ale większość zastosowań tego wzorca obejmowałaby tak wiele obiektów, że lepiej byłoby użyć a ThreadLocaldo przechowywania odniesienia do zestawu skrótów, który mapuje obiekty na wystąpienia dla poszczególnych wątków.
supercat

@optional Oznacza to po prostu, że każda instancja niestatyczna ThreadLocalbędzie przechowywać swoje własne dane lokalne wątku, nawet jeśli te ThreadLocalinstancje istnieją w tym samym wątku. Niekoniecznie jest to złe - przypuszczam, że może to być najmniej popularny wzór z tych dwóch
zrób to

17

Albo uczyń go statycznym, albo jeśli próbujesz uniknąć jakichkolwiek statycznych pól w swojej klasie - utwórz samą klasę jako singleton, a następnie możesz bezpiecznie używać poziomu wystąpienia ThreadLocal, o ile masz ten singleton dostępny globalnie.



3

Powodem jest to, że dostęp do zmiennych uzyskuje się poprzez wskaźnik powiązany z wątkiem. Działają jak zmienne globalne z zasięgiem wątku, dlatego statyczne jest najbliższym dopasowaniem. W ten sposób uzyskujesz stan lokalny wątku w rzeczach takich jak pthreads, więc może to być po prostu przypadek historii i implementacji.


1

Zastosowanie Threadlocal na instancji na wątek polega na tym, że chcesz, aby coś było widoczne we wszystkich metodach obiektu i aby było bezpieczne wątkowo bez synchronizowania dostępu do niego, tak jak w przypadku zwykłego pola.


1

Odnieś się do tego , aby uzyskać lepsze zrozumienie.

Krótko mówiąc, ThreadLocalobiekt działa jak mapa klucz-wartość. Kiedy ThreadLocal get/setmetoda wywołania wątku pobierze / zapisze obiekt wątku w kluczu mapy, a wartość w wartości mapy. Dlatego różne wątki mają różne skopiowane wartości (które chcesz przechowywać lokalnie), ponieważ znajdują się one we wpisach różnych map.

Dlatego potrzebujesz tylko jednej mapy, aby zachować wszystkie wartości. Chociaż nie jest to konieczne, możesz mieć wiele map (bez deklarowania statycznego), aby zachować również każdy obiekt wątku, co jest całkowicie redundantne, dlatego preferowana jest zmienna statyczna.


-1

static final ThreadLocal zmienne są bezpieczne dla wątków.

staticsprawia, że ​​zmienna ThreadLocal jest dostępna w wielu klasach tylko dla odpowiedniego wątku. jest to rodzaj deklaracji zmiennych globalnych odpowiednich zmiennych lokalnych wątku w wielu klasach.

Możemy sprawdzić bezpieczeństwo tego wątku za pomocą następującego przykładu kodu.

  • CurrentUser - przechowuje bieżący identyfikator użytkownika w ThreadLocal
  • TestService- Prosta usługa z metodą - getUser()aby pobrać bieżącego użytkownika z CurrentUser.
  • TestThread - ta klasa służy do tworzenia wielu wątków i jednoczesnego ustawiania identyfikatorów użytkowników

.

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

}

}

.

Uruchom główną klasę TestThread. Wynik -

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
...

Podsumowanie analizy

  1. Wątek „główny” uruchamia się i ustawia bieżącego użytkownika na „62”, równolegle uruchamia się wątek „ForkJoinPool.commonPool-worker-2” i ustawia aktualnego użytkownika na „31”, równolegle uruchamia się wątek „ForkJoinPool.commonPool-worker-3” i ustawia bieżący użytkownik jako "81", równolegle "ForkJoinPool.commonPool-worker-1" rozpoczyna się wątek i ustawia bieżącego użytkownika na "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. Wszystkie powyższe wątki będą spać przez 3 sekundy
  3. mainwykonanie kończy się i wyświetla aktualnego użytkownika jako "62", równolegle ForkJoinPool.commonPool-worker-1wykonywanie kończy się i wyświetla bieżącego użytkownika jako "87", równolegle ForkJoinPool.commonPool-worker-2kończy się wykonywanie i wyświetla aktualnego użytkownika jako "31", równolegle ForkJoinPool.commonPool-worker-3wykonywanie kończy się i wyświetla aktualnego użytkownika jako "81"

Wnioskowanie

Współbieżne wątki mogą pobierać poprawne identyfikatory użytkowników, nawet jeśli zostały zadeklarowane jako „statyczne końcowe ThreadLocal”

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.