Widzialność
Jeśli nie zastosowana jest synchronizacja kompilator może zmienić kolejność wykonywania poleceń więc jeśli dwie zmienne są przypisywane jedna po drugiej - nie ma pewności że ich wartości będą widoczne w tym samym czasie dla innego wątku w tej samej kolejności. Możliwe jest widzenie starych wartości (stale data) a w przypadku zmiennych 64bitowych możliwe jest widzenie połowy wartości z dwóch wartości którą ta zmienna miała ponieważ ich zamiana jest robiona w dwóch krokach.
Jednym z rozwiązań jest stosowanie blokad - jeśli wątek uzyska blokadę następnie zmienni zmienną i zwolni blokadę, to inny wątek który uzyskał tą samą blokadę będzie widzieć wszystkie dane.
Innym przykładem są zmienne volatile. Jeśli jeden wątek A zapisuje do zmiennej volatile wartość a następnie inny wątek B ją czyta to zobaczy dobrą wartość zmiennej jak i innych wcześniej zmienianych zmiennych przez wątek A.
Przykład: Wątek Reader nie widzi zmiennej ready więc będzie działał w nieskończoność
public class NoVolatile {
private static boolean ready = false;
private static class Reader extends Thread {
public void run() {
int i = 0;
while(!ready) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
new Reader().start();
TimeUnit.SECONDS.sleep(1);
ready = true;
}
}
Po zastosowaniu słowa kluczowego volatile program się zakończy po sekundzie jak przewidywano:
public class VolatileExample {
private static volatile boolean ready = false;
private static class Reader extends Thread {
public void run() {
int i = 0;
while(!ready) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
new Reader().start();
TimeUnit.SECONDS.sleep(1);
ready = true;
}
}
Powiązanie z wątkiem - thread confinement
Jeśli dane (klasa) nie jest dostępna dla wielu wątków wtedy nie trzeba stosować synchronizacji ani tworzyć klas thread-safe. Przykładem jest Swing gdzie jest jeden wątek odpowiedzialny za obsługę eventów. Mechanizm ten jest mechanizmem który może być jedynie zaznaczony w dokumentacji - nie da się wymusić aby klasa była używana tylko w jednym wątku.
Przykłady powiązań z wątkiem:
- zmienna volatile którą czyta wiele wątków ale zmienia tylko jeden
- zmienna lokalna w metodzie - jest powiązana ze stosem wywołania
- zmienna ThreadLocal - dla każdego wątku inna instancja
Niezmienność - Immutability
Klasy immutable są zawsze thread-safe. Mogą mieć tylko jeden stan który jest ustawiany przy tworzeniu klasy.
Klasa jest niezmienna (immutable) jeśli:
- jej stan nie może być zmieniony po konstrukcji
- wszystkie pola są final
- jest poprawnie stworzona - jej referencja nie "ucieknie" podczas konstrukcji
Publikacja i ucieczka
Publikacja to udostępnianie zmiennych na zewnątrz.
Ucieczka this jest możliwa np:
public class Escape {
public Escape(EventSource eventSource) throws InterruptedException {
eventSource.registerListener(new EventListener() {
@Override
public void onEvent() {
someMethod();
}
});
}
}
Aby bezpiecznie opublikować obiekt obie referencja obiektu jak i jego stan muszą być widoczne dla innych wątków w tym samym czasie. Aby tego dokonać należy:
- zainicjować obiekt ze statycznego inicjatora - inicjowanie przy deklarowaniu statycznego pola
- trzymać jego zmienną w polu volatile albo AtomicReference
- trzymać jego zmienną w polu final w poprawnie skonstruowanym obiekcie
- trzymać jego zmienną w polu który jest poprawnie chronione przez blokadę ( tyczy się także kolekcji synchronizowanych albo innych bezpiecznych kolekcji jak ConcurentHashMap, Vector, BlockingQueue itp
Jeśli chodzi o obiekty zmienne - muttable - to należy je bezpiecznie publikować jak i muszą być thread-safe albo muszą być chronione przez blokadę.
Jeśli chodzi o obiekty immutable - to nie muszą być one publikowane bezpiecznie - mogą być jakkolwiek - np przez pole public.
Brak komentarzy:
Prześlij komentarz