Wzorzec ThreadLocal w praktyce
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.
Mateusz Mazurek