Java zaawansowane #14: widoczność zmiennych (volatile)

Jednym z wielu problemów obok synchronizacji, jest widoczność zmiennych klasowych dla poszczególnych wątków. Nie jest to problem oczywisty, dlatego rozważę wpierw poniższy kod.

public class VolatileVariable {
	private static boolean done = false;
	
	public static void main(String[] args) {
		Runnable starting = () -> {
			for (int i = 0; i < 1000; i++) {
				System.out.println("Counter: " + i);
			}
			done = true;
		};
		
		Runnable ending = () -> {
			int i = 1;
			while(!done) i++;
			System.out.println("Last item: " + i);
		};
		
		Executor executor = Executors.newCachedThreadPool();
		executor.execute(starting);
		executor.execute(ending);
	}
}

Na konsoli powinno wyświetlić się:

Counter: 1
Counter: 2
...
Counter: 998
Counter: 999

Wynik nie jest poprawny, zmiana flagi done, nigdy nie została odczytana. Dzieje się tak dlatego, ponieważ słowo kluczowe static pozwala komputerowi na trzymanie tej wartości w pamięci podręcznej. Z punktu widzenia kompilatora, jest to wygodne rozwiązanie, pamięć komputera jest ograniczona, więc w ten sposób JVM może przyśpieszać pracę na odpowiednich wartościach. Problem zaczyna się pojawiać w środowisku wielowątkowym, gdy kilka wątków pracuje na tej samej zmiennej statycznej. Jeśli komputer zawiera więcej niż jeden procesor, każdy wątek może działać na innym procesorze. Ponieważ każdy procesor ma swoją pamięć podręczną, wartość zmiennej dla każdego z nich może być różna. Dodatkowo procesor ma możliwość stosować techniki optymalizacyjne, zmieniając kolejność wykonywanych rozkazów (oczywiście jeśli nie wpływa to na wynik końcowy obliczeń).

Zmienna volatile

Aby poprawić powyższy kod, wystarczy z korzystać ze specjalnego modyfikatora volatile. Nakładasz go na zmienną klasową, która jest współdzielona przez więcej niż jeden wątek. Skorzystanie z tego modyfikatora, gwarantuje widoczność rzeczywistej wartości pola dla wszystkich wątków Javy.

private static volatile boolean done = false;

Dzięki tej zmianie drugi wątek zostanie odblokowany:

...
Counter: 996
Counter: 997
Counter: 998
Counter: 999
Last item: 19063057

Problemy przy użyciu volatile

Niestety udostępnianie rzeczywistej wartości zmiennej dla każdego wątku może doprowadzić do popularnego problemu w zakresie wielowątkowości, o nazwie wyścig. Dzieje się tak dlatego, że volatile nie jest alternatywą dla synchronizacji wątków. Jeśli potrzebujesz pracować na tej samej sekcji krytycznej, prawdopodobnie nie unikniesz potrzeby synchronizacji. Jedyną sensowną sytuacją kiedy warto rozważyć volatile jest użycie go na zmiennej, na której wykonywana jest operacja atomowa.

Widoczność zmiennych w wątkach

Istnieją też inne sposoby, aby zapewnić widoczność zmiennej:

  • skorzystanie z modyfikatora final
  • użycie blokad
  • początkowa wartość zmiennej statycznej będzie widoczny przy skorzystaniu z inicjalizacji statycznej
  • skorzystanie ze zmiennych atomowych

Kod do lekcji: https://github.com/developeronthego/java-advanced/tree/master/src/main/java/advanced/lesson14

Może Ci się również spodoba

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *