niedziela, 29 czerwca 2014

Wielowątkowość - używanie ThreadPoolExecutora

Powiązanie między zadaniami a ich polityka wykonywania

 

Można powiedzieć że ExecutorService rozdziela tworzenie zadań od ich wykonywania - jednak jest to prawdą tylko wtedy gdy zadania są niezależne.
Nie wszystkie zadania można łatwo wywoływać używając ExecutorService np:
  • zadania zależne - jeśli jedno zadanie zależy od innego to w przypadku Executora z ograniczoną liczbą wątków może dojść do zakleszczenia: thread starvation deadlock, najprostszym przykładem jest wykonywanie zadania na Executorze ograniczonym do jednego wątku które wywołuje inne zadanie na tej samej "puli" i czekanie na wynik. Taki wątek będzie czekał w nieskończoność bo wątek na którego czeka nigdy się nie zacznie - należy pamiętać że najlepiej w tym wypadku stosować executor z nieograniczoną liczbą wątków
  • zadania które wykorzystują "przywiązanie do wątku" (thread confinement) - takie zadania powinny być wykonywane na executorze z jednym wątkiem - zmiana liczby wątków może spowodować duże problemy
  • zadania które wykorzystują zmienne ThreadLocal - należy pamiętać że dla każdego zadania nie jest tworzony nowy wątek - jest on wykorzystywany ponownie, dodawane są też nowe i usuwane nieużywane. To powoduje że jest sens korzystania z tych zmiennych tylko jeśli zadanie zawsze wyczyści daną zmienną przed zakończeniem działania
  • zadania które powinny się skończyć szybko - niektóre zadania powinny się skończyć szybciej od innych np zadanie związane z GUI - jeśli Executor jest zapełniony długimi zadaniami tą zadanie GUI może na tym ucierpieć
Najlepszymi zadaniami do wykonywania w jednym Executorze są zadania o podobnej wielkości i niezależne.

Zrównolegalnie algorytmów rekursywnych

 

Jeśli wykonujemy pętlę i jej iteracje nie zależą od siebie a praca w nich wykonywana jest na tyle duża że jej wykonanie równoważy pracę związaną z tworzeniem nowego zadania można ją wykonać równolegle:

public void processSequencially(List<String> elements) {
        for (String element : elements) {
            processElement(element);
        }
    }
public void processInParallel(List<String> elements) {
        for (final String element : elements) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    processElement(element);
                }
            });
           
        }
    }


Tak samo jak zrównoleglenie pętli można zrównoleglić algorytm rekurencyjny - na takich samych warunkach niezależności:

    public void recursiveSequencial(List<Node> nodes, List<String> results) {
        for (Node node : nodes) {
            processNode(node, results);
            recursiveSequencial(node.getChildren(), results);
        }
    }
   
    public void recursiveParallel(List<Node> nodes, final List<String> results) {
        for (final Node node : nodes) {
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    processNode(node, results);
                }
            });
            processNode(node, results);
           
            recursiveParallel(node.getChildren(), results);
        }
    }


Należy pamiętać że kiedy submitujemy zadania do executora należy poczekać na skończenie zadań przez CompletionService lub wykonując zamykanie normalne - sutdown i awaitTermination wtedy wszystkie zadania zostaną zakończone.

środa, 25 czerwca 2014

Wielowątkowość - anulowanie zadań, kończenie serwisów

Anulowanie zadań

 

Najprościej anulowanie zadań można dokonać poprzez zmienną lokalną volatile np. finish. Gdzie zmienna będzie co pewien okres czasu sprawdzana i zadanie będzie można zakończyć.

public class CancellingUsingLocalVariable {
    public static void main(String[] args) throws InterruptedException {
        CancelledTask task = new CancelledTask();
        Thread thread = new Thread(task);
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        task.cancel();
    }
    private static class CancelledTask implements Runnable {  
        private volatile boolean cancelled = false;
        public void run() {
            while (!cancelled) {
                try {
                    doWork();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        private void doWork() throws InterruptedException {
            TimeUnit.SECONDS.sleep(1);
        }
        public void cancel() {
            this.cancelled = true;
        }
    }
}


Jednak jeśli wątek czeka na metodzie blokującej to musi się ona zakończyć zanim sprawdzony będzie ponownie status, to powoduje że nie jest to idealna metoda na anulowanie zadań.

Mechanizm wątków nie ma bezpośredniego mechanizmu aby zatrzymać inny wątek. Można jednak wykorzystać mechanizm przerwania - każdy wątek posiada zmienną interrupted którą można ustawić za pomocą metody interrupt(). Można sprawdzić za pomocą metody isInterrupted(). Statyczna i źle nazwana metoda statyczna interrupted() resetuje flagę i zwraca jej poprzednią wartość.

Większość metod blokujących co jakiś czas sprawdza flagę interrupted, czyści ją i wyrzuca wyjątek InterruptedException.

Mechanizm przerwania (interruption) jest najlepszą metodą anulowania zadań.
Samo ustawianie flagi nie jest jednoznaczne z przerwaniem wątku - to wątek ustala swoją politykę obsługi przerwań.

Należy pamiętać że działając bezpośrednio na zadaniu (Runnable, Callable) nie powinno się połykań błędy Interrupted exception a powinno się ustawić ponownie flagę interrrupted w aktualnym wątku:
Thread.currentThread().interrupt(). Należy pamiętać że jeśli używamy statycznej metody interrupted() i zwraca ona true jeśli nie rzucamy wyjątku powinniśmy chociać przywrócić daną flagę bo może jest ona obsługiwana przez wątek wykonujący zadanie. Należy pamiętać że to wątek ustala swoją politykę obsługi flagi interrupted!

public class CancellingUsingInterruption {
    public static void main(String[] args) throws InterruptedException {
        CancelledTask task = new CancelledTask();
        Thread thread = new Thread(task);
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        thread.interrupt();
    }
    private static class CancelledTask implements Runnable {  
        public void run() {
            while (Thread.getCurrentThread().isInterrupted()) {
                try {
                    doWork();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }       
        private void doWork() throws InterruptedException {
            TimeUnit.SECONDS.sleep(1);
        }
    }
}



Najlepszą jednak metodą anulowania zadań jest korzystanie z obiektów zwracanych przez ExecutorService: Future. Reprezentują one rezultat zadania wykonywanego asynchronicznie.

public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<?> future = executor.submit(new CancelledTask());
        TimeUnit.SECONDS.sleep(3);
        future.cancel(true);
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.DAYS);
    }







Jednak inną ważną zaletą interfejsu future jest to że przy pobieraniu rezultatów za pomocą metody get wszystkie błędy które zakończyły wykonywanie zadania są znów wyrzucane opakowane w ExecutionException.
Przykład wykonywania zadania przez 5 sekund po czym anulowanie zadania i pokazanie błędu jeśli wcześniej zakończył wykonywanie zadania:

public class CancellingAfterSomeTime {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<String> future = executor.submit(new CancelledTask()); 
        try {
            String result = future.get(5, TimeUnit.SECONDS);
            System.out.println("Task returned: " + result);
        } catch (ExecutionException e) {
            System.out.println("Task throwed exception " + e.getCause().getMessage());
        } catch (TimeoutException e) {
            System.out.println("Timeout - cancelling task");
            future.cancel(true);
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.DAYS);
    }
    private static class CancelledTask implements Callable<String> {
        private Random r = new Random();
        public String call() throws Exception {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    if (r.nextDouble() > 0.85) {
                        throw new RuntimeException("Exception from task");
                    }
                    if (r.nextDouble() > 0.75) {
                        // found result
                        break;
                    }
                    doWork();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            return "result";
        }     
        private void doWork() throws InterruptedException {
            TimeUnit.SECONDS.sleep(1);
        }
    }

.

Metody nieodpawiadające na przerwanie


Nie wszystkie metody blokujące sprawdzają falgę interrupted i wyrzucają wyjątek InterrruptedException np:
  • czekanie na blokadzie na metodzie lock obiektu Lock lub na metodzie/bloku synchronized - w przypadku obiektu Lock można zamienić wywołanie metody na lockIterruptibly
  • synchroniczne wywołania I/O z java.io - metody read i write w OutputStream i InputStream nie odpowiadają na przerwanie. Aby je zakończyć należy zamknąć powiązany obiekt Socket i metoda zostanie skończona - w takim wypadu należy przeciążyć metodę interrupt i zamknąć socket co spowoduje zakończenie metody read i write

Zatrzymywanie serwisów opartych na wątkach

 

Jeśli w aplikacji występuje serwis który sam zarządza swoimi wątkami i działa on nieustannie to przy zamykaniu aplikacji należy także zakończyć jego wątki ponieważ inaczej JVM nie zamknie się samoczynnie.
W tym wypadku należało by napisać mechanizm który będzie to robił - najlepiej wzorować się na takich właśnie serwisach - ExecutionService. Metody shutdown, awaitTermination, shutdownNow.

 

niedziela, 22 czerwca 2014

Wielowątkowość - Executor framework

Wykonywanie zadań bezpośrednio

 

Bezpośrednio tworzenie i wykonywanie zadań ma szereg wad:
  • tworzenie i kończenie pracy wątku nie jest darmową operacją i opóźnia wykonanie wątku, jeśli wątków jest wiele a zadanie do wykonania proste to uwidacznia się to jeszcze bardziej zwiększając drastycznie opóźnienie (latency)
  • każdy wątek ma swój stos - konsumuje pamięć którą jest przydzielona JVM
  • nieograniczenie tworzenie wątków może spowodować że systemowi zabraknie zasobów i przestanie działać
 

Executor serivce

 

Executor framework pozwala na oddzielenie tworzenia zadań i od ich uruchamiania. Do uruchamiania zadań służy np klasa ThreadPoolExecutor.

Aby stworzyć executora do wykonywania zadań można skorzystać z klasy Excutors i metody fabrykującej newFixedThreadPool dla executora ze stałą liczbą wątków lub newCachedThreadPool dla executora bez ograniczenia liczby wątków.

Executor nie tworzy dla każdego zadania nowego wątku - jeśli wątek zakończy pracę, bierze następne zadanie a do kolejkowania zadań służy kolekcja blokująca (BlockingQueue).

Używanie interfejsu ExecutorService pozwala nam w każdym momencie podmienić sposób wykonywania zadań, i ustalić wiele innych właściwości:
  • w którym wątku zadanie będzie wykonywane
  • w jakiej kolejności zadania będą wykonywane
  • ile zadań może działać naraz
  • jak wiele zadań może być kolejkowanych
  • specyfikacje czy task może być odrzucony z powodu przeciążenia i w jaki sposób ma to być zrobione
  • czy przed i po wykonaniu zadania wykonać inne zadanie
Wszystko to można dokonać przez użycie bezpośrednio konstruktorów np klasy ThreadPoolExecutor.

ExecutorService ma trzy stanu - running, shutting down, terminated. Metoda shutdown zaczyna bezpieczne zamykanie - wszystkie submitowane i działające taski są kończone a następnie executor jest zamykany. Możliwe jest też użycie metody shotdownNow które stara się anulować aktualnie działające zadania i nie startuje nowych.
Należy pamiętać że aby maszyna wirtualna się zamknęła nie mogą działać żadne jej wątki które nie są demonami więc należy zamknąć ExecutorService aby skończyć jego wątki. Najlepiej zrobić to wykonując metodę shutdown a następnie awaitTermination z odpowiednio dużym czasem.

Inne rodzaje ExecutorService:
  • ForkJoinPool - do wykonywania specjalnych zadań ForkJoinTask  które umożliwiają łatwą implementacje problemów które można podzielić na mniejsze
  • ScheduledThreadPoolExecutor - do tworzenia zadań które będą dostępne (wykonywane) w określonym czasie
 

CompletionService


Pakiet java.util.concurent zawiera także interfejs CompletionService i jego klasę implementującą ExecutorCompletionService. Dzieki tej klasie można w łatwy sposób pobrać wyniki wykonanych zadań jeśli są one potrzebne - nie trzeba pobierać Future dla każdego zadania i iterować po tym. Dzięki temu dostaniemy pierwszy wynik zaraz po jego zakończeniu bo wyniki zapisywane są na kolejce blokującej.
ExecutionCompletionService potrzebuje executora aby działać - więc można tworzyć wiele takich serwisów dla jednego executora, można także przekazać implementację kolejki blokującej której chcemy użyć dla skończonych zadań.

sobota, 21 czerwca 2014

Wielowątkowość - kolekcje synchronizowane i wielowątkowe, synchronizatory

Kolekcje synchronizowane


Java zawiera kolekcje synchronizowane - stare kolekcje Vector i Hashtable. Można także z każdej niesynchronizowanej kolekcji zrobić taką poprzez metodę fabrykującą Collections.synchronizedXXX np synchronizedMap. Fabryka taka tworzy obiekt który dekoruje każdą publiczną metodę opakowując ją blokiem synchronized używając instancji obiektu do blokowania.

Kolekcje synchronizowane nie mają takich metod jak putIfAbsent, ich iteratory są iteratorami fail-fast - czyli jeśli wykryją modyfikacje w trakcie iterowania wyrzucają wykątek ConcurrentModificationException. Blokowanie iteratora na czas iterowania może być bardzo kosztowne i sprawiać że program nie będzie skalowalny.


Kolekcje wielowątkowe (concurrent collections)

 

W javie 5 wprowadzono kolekcje wielowątkowe takie jak ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteArraySet, ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDequeue

ConcurrentHashMap nie synchronizuje wszystkich metod zamiast tego wykorzystywany jest tu mechanizm podziału na wiele map każda ze swoją blokadą - lock striping dzięki czemu więcej wątków może symultanicznie wykonywać operacje na obiekcie mapy. Dodatkowo mapa implementuje interfejs ConcurentMap w którym są dodatkowe atomowe metody: putIfAbsent, remove, replace. Iterator takiej mapy nie wyrzuca wyjątku jeśli mapa jest modyfikowana jednak jest iteratorem weakly consistent - czyli nie musi pokazywać zmian po utworzeniu iteratora.

CopyOnWriteArrayList jest klasą thread-safe i zastępuje ArrayList - tworzy ona kopie podległej wewnętrznej tablicy za każdym razem kiedy wykonywana jest operacja dodawania, lub usuwania obiektu - dzięki temu iterator po stworzeniu może iterować na tablicy na której został zainicjowany więc nie wyrzuci wyjątku ConcurrentModificationException. Klasa taka ma zastosowanie tam gdzie dominującą operacją jest iterowanie a nie dodawanie np. listenery.

Kolejki blokujące mogą być użyte do rozwiązania problemu producent-konsumer. Zawierają metody blokujące które czekają jeśli kolejka jest pusta lub pełna.

Kolejki dequeue umożliwają pobieranie elementów także z początku kolejki dzięki czemu mogą być używane w alborytmach work-stealing w których każdy konsument ma jedną kolejkę i jeśli nie ma więcej pracy może wziąć pracę z początku innego konsumenta - dzięki temu wątki mniej będą ze sobą walczyły (less contention) i dzięki temu rozwiązanie jest bardziej skalowalne.


Metody blokujące które rzucają wyjątek IterruptedException


Każdy wątek może zostać zablokowany lub zapauzowany z różnych powodów - oczekiwaniu na blokadzie, czekanie na skończenie operacji I/O, czekanie na wybudzenie z operacji sleep. W takim wypadku proces przechodzi w stan BLOCKED, WAITING, TIMED_WAITING. Wątek który jest zablokowany musi być czekać na operacje na którą nie ma wpływu a która go wybudzi.

Nie da się zatrzymać wątku z innego wątku - można mu tylko ustawić flagę interrupt poprzez metodę interrupt. Mechanizm ten jest w większości wykorzystywane to anulowania długo trwających operacji.

Jak obsłużyć InterruptedException:
  • poprzez propagowanie go wyżej do metody wywołującej
  • poprzez obsługę - jeśli nie można go wyżej propagować - poprzez restorowanie flagi interrupted: Thread.currentThread().interrupt()
Można też pominąć obsługę jeśli pracujemy bezpośrednio na klasie Thread.

Synchronizatory

 

Semaphore - klasa przydatna np przy pulach obiektów. 

CountDownLatch - jednorazowy zatrzask który odlicza się on n do zera, wątki czekają na to aż wartość będzie równa 0. Może być użyty tylko raz.

CyclicBarrier - tak jak zatrzask blokują grupę wątków aż pewna aktywność się skończy - znana - stała liczba wątków dojdzie do bariery, następnie bariera jest resetowana a wątki mogą kontynuwać działanie. Jeśli wątek na barierze zostanie przerwany lub wystąpi timeout to reszta wątków zostanie wybudzona przez BrokenBarrierException a bariera zostanie w stanie broken. 

Phaser - jest bardziej wyrafinowaną barierą, umożliwia rejestracje i wyrejestrowywanie wątków oczekujących.

Exchanger pozwala na wymianę obiektu między wątkami w miejscu "spotkania".

 

 

piątek, 20 czerwca 2014

Wielowątkowość - współdzielenie obiektów

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
Klasa (instancja) immutable  może być bezpiecznie publikowana nawet bez użycia synchronizacji! Wystarczy że została poprawnie stworzona.

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
Część obiektów można nazwać efektywnie niezmiennymi - czyli nie są one immutable ale po stworzeniu nie są zmieniane aby ich używać należy tylko je bezpiecznie publikować.
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.


czwartek, 19 czerwca 2014

Wielowątkowość - Thread Safety

 Thread safety


Jeśli wiele wątków dostaje się/może się dostać do zmiennej stanu bez jakiejkolwiek synchronizacji to program nie będzie działał dobrze, aby tak się stało musi być zrobiona jedna z poniższych rzeczy:
  • zmienna stanu nie powinna być dostępna
  • zmienna stanu powinna być niezmienna (immutable)
  • powinno się użyć synchronizacji przy dostępie do zmiennej

Jak zdefiniować klasę która jest thread-safe?
Klasa taka działa dobrze kiedy jest dostępna/używana przez wiele wątków bez względu na przeplatanie się i harmonogram ich działania bez dodatkowej synchronizacji po stronie kodu wywołującego. Klasa taka zawiera wszystkie mechanizmy synchronizacji w sobie.

Klasy bez stanu są zawsze thread-safe.

Atomowość 

 

Problemem mogą być operacje złożone dla których mogą wystąpić race-conditions.
Dwa typy race-conditions:
  • check-then-act
  • read-modify-write
Przykłady race-conditions:
  • "singleton" class z niesynchronizowaną metodą getInstance()
  • niesynchronizowana operacja zwiększania licznika count++
Operacje A and B są wzajemnie do siebie atomowe jeśli z perspektywy wątku wykonującego A jeśli inny wątek wykonuje B to jest ona wykonana w całości albo w ogóle.

Blokowanie 

 

Klasa która ma jedną zmienną tworzącą stan może być thread safe jeśli zmienna jest atomowa (np AtomicLong), po dodaniu drugiej już tak nie musi być jeśli zmienne nie są niezależne. Aby zmiana zmiennych była atomowa należy zastosować blokowanie poprzez użycie bloków lub metod synchronized. Ten rodzaj blokad nazywa się intrinsic lock. Blokady te są wielo-wejściowe (reentrant) więc wątek może uzyskać dostęp do blokady ponownie jeśli już go ma.

Strzeżenie zmiennych blokadami


Aby złożone akcje (check-then-act, read-modify-write) były atomowe muszą się znaleźć w bloku synchronizowanym przez blokadę.

Dla każdej zmiennej do której może mieć dostęp wiele wątków wszystkie dostępy muszą być przeprowadzone z tą samą blokadą.

Dostęp do zmiennych powiązanych musi się odbywać na tej samej blokadzie.

Żywotność i wydajność

 

Należy zmniejszać bloki synchronizowane tylko na czas zmian zmiennych, nie należy w nich wykonywać długich operacji szczególnie tych związanych z siecią i dostępem do I/O.