Wzorzec ThreadLocal w praktyce

Cześć! Cieszę się, że mnie odwiedziłeś/aś. Zanim przejdziesz do artykułu chciałbym zwrocić Ci uwagę na to, że ten artykuł był pisany kilka lat temu (2014-11-29) miej więc proszę na uwadzę że rozwiązania i przemyślenia które tu znajdziesz nie muszą być aktualne. Niemniej jednak zachęcam do przeczytania.

Cześć,
dawno niczego nie pisałem tutaj, niestety czas nie pozwala mi prowadzić tego bloga tak systematycznie jakbym chciał. Dziś jednak znalazło się trochę czasu między pisaniem pracy inż, pracą, dziewczyną i życiem – co zaowocowało takim oto, krótkim wpisem.

Zajmiemy się dziś wzorcem ThreadLocal, czyli rozwiązaniem problemu, kiedy to chcemy aby jakaś zmienna udostępniała swoją kopię każdemu wątkowi który o nią zapyta.

Spójrzmy na taki scenariusz:

Mamy klasę o nazwie VariableContainer. Zawiera ona jedno pole typu int o nazwie variable. Jest to pole statyczne, gdyż chcemy aby odnosił się on do typu a nie konkretnego obiektu. I takiej konstrukcji używamy w naszym projekcie.

Teraz zdarza się jakiś case, gdzie musimy podzielić naszą aplikację na wątki. A więc n wątków będzie pracować na naszej zmiennej. Niestety podczas pisania tego kodu, okazuje się że każdy wątek powinien mieć w swojej pamięci lokalnej kopię naszej zmiennej variable i dopiero na niej pracować… Jak to rozwiązać? Zerknijmy pierw na kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class VariableContainer
{
  public static void main(String args[]) {
      for(int i=0;i<20;i++)
      {
          NamedThread nt = new NamedThread(i);
          nt.start();
      }
     
      System.out.println("Main: "+VariableContainer.get());
  }

  public VariableContainer() {
  }
  private static int variable = 0;
 
  public static void increaseBy(int howMuch){
      variable+=howMuch;
  }
  public static int get(){
      return variable;
  }
}

gdzie NamedThread to klasa dziedzicząc po wątku, symuluje ona pracę na zmiennej variable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NamedThread extends Thread {

  private int number;
 
  public NamedThread(int number) {

      this.number=number;
     
  }
  @Override
  public void run() {
      VariableContainer.increaseBy(4);
      System.out.println("Value for thread nr."+number+" is:"+VariableContainer.get());
  }
 
}

Kod taki jak wyżej oczywiście daje na wyjściu bzdury:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Value for thread nr.1 is:4
Value for thread nr.2 is:12
Value for thread nr.0 is:8
Value for thread nr.5 is:16
Value for thread nr.3 is:24
Value for thread nr.6 is:24
Main: 36
Value for thread nr.17 is:40
Value for thread nr.13 is:36
Value for thread nr.10 is:44
Value for thread nr.9 is:32
Value for thread nr.14 is:48
Value for thread nr.18 is:52
Value for thread nr.4 is:52
Value for thread nr.7 is:56
Value for thread nr.8 is:60
Value for thread nr.11 is:64
Value for thread nr.12 is:68
Value for thread nr.15 is:72
Value for thread nr.16 is:76
Value for thread nr.19 is:80

To co widzimy jest wielce dalekie od zamierzonego efektu. Chcielibyśmy aby wartość zmiennej variable nie była dzielona pomiędzy wątki.
Możemy do tego użyć wzorca ThreadLocal. Istnieje klasa opakowująca o tej samej nazwie w Javie. Możemy użyć jej w taki sposób:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class VariableContainer

{
  public static void main(String args[]) {
     
      for(int i=0;i<20;i++)
      {
          NamedThread nt = new NamedThread(i);
          nt.start();
      }
     
      System.out.println("Main: "+VariableContainer.get());
  }

  private static ThreadLocal<Integer> variable = new ThreadLocal<Integer>(){
      @Override protected Integer initialValue(){
          return 0;
      }
  };
 
  public static void increaseBy(int howMuch){
      variable.set( variable.get()+howMuch );
  }
  public static int get(){
      return variable.get();
  }
}

Dzieki takiej zmianie kodu, wyjście prezentuje się znaczenie poprawniej:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Value for thread nr.1 is:4
Value for thread nr.5 is:4
Value for thread nr.9 is:4
Value for thread nr.2 is:4
Value for thread nr.13 is:4
Value for thread nr.6 is:4
Value for thread nr.17 is:4
Main: 0
Value for thread nr.10 is:4
Value for thread nr.0 is:4
Value for thread nr.14 is:4
Value for thread nr.18 is:4
Value for thread nr.3 is:4
Value for thread nr.4 is:4
Value for thread nr.7 is:4
Value for thread nr.8 is:4
Value for thread nr.11 is:4
Value for thread nr.12 is:4
Value for thread nr.15 is:4
Value for thread nr.16 is:4
Value for thread nr.19 is:4

Jak widać, teraz zmienna wartość zmiennej variable jest dzielona między wątki, zachowujemy wartość odpowiednią dla wątku głównego oraz dajemy, każdemu innemu, swoją własną kopię wartości tej zmiennej.

Warto znać takie zastosowania, czasem się przydają ;)

Pozdrawiam,
Mateusz M.

Dzięki za wizytę,
Mateusz Mazurek
Mateusz M.

Ostatnie wpisy

Python 1.0 vs. 3.13: Co się zmieniło?

Cześć. Dziś luźny artykuł, bo dziś pobawimy się jedną z pierwszy wersji Pythona. Skompilujemy go i zobaczymy co tam w… Read More

1 tydzień ago

Podsumowanie: styczeń i luty 2025

Nowy rok czas zacząć! Więc lećmy z podsumowaniem. Nowy artykuł Nie uwierzycie, ale pojawił się na blogu nowy artykuł! Piszę… Read More

3 tygodnie ago

Just-in-time compiler (JIT) w Pythonie

Cześć! W Pythonie 3.13 dodano JITa! JIT, czyli just-in-time compiler to optymalizacja na która Python naprawdę długo czekał. Na nasze… Read More

1 miesiąc ago

Podsumowanie roku 2024

Cześć! Zapraszam na podsumowanie roku 2024. Książki W sumie rok 2024 był pod względem ilości książek nieco podobny do roku… Read More

1 miesiąc ago

Podsumowanie: wrzesień, październik, listopad i grudzień 2024

Podtrzymując tradycję, prawie regularnych podsumowań, zapraszam na wpis! Nie mogło obyć się bez Karkonoszy We wrześniu odwiedziłem z kolegą Karkonosze,… Read More

2 miesiące ago

Podsumowanie: maj, czerwiec, lipiec i sierpień 2024

Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More

6 miesięcy ago