Inspiracja , wiedza , realizacja
Jsystems

W przebudowie

Login



Java

Oracle

Linux

Android

PostgreSQL

Microsoft SQL Server

Synchronizacja wątków

Kamil Perczyński
Data dodania: Oct 30, 2015
Data aktualizacji: Oct 30, 2015

W tym artykule zajmiemy się problemem współdzielenia zasobów przez kilka wątków. Zakładam, że czytelnik poznał już podstawy programowania w Javie, a także podstawową wiedzę o wątkach. Mam tu na myśli używanie obiektów Thread i Runnable.

Błędy wynikające z braku synchronizacji

Rozważmy poniższy przykład. Uruchamiam dwa wątki, które wpisują elementy do listy. Po pętli, każdy z nich wpisuje rozmiar kolekcji. Przy wywołaniu dodawania 10 000 razy spodziewamy się, że któryś z wątków (oczywiście nie wiemy który) wypisze wynik 20 000.

Wynik jest jednak nieco inny:

Ale możemy dostać również taki wynik (cały czas wykonujemy ten sam kod):

Problem leży właśnie w braku synchronizacji. Teraz kilka wątków naraz może wywołać procedurę dodawanie elementu. Jeśli jeden wątek wywoła add(), w trakcie dodawania innego elementu może zdarzyć się tak, że pewne elementy zginą. Jeśli jeden wątek wywoła add(), w trakcie rozszerzania tablicy na której pracuje ArrayList - wtedy drugi wątek próbuje wstawić element na nieistniejący indeks - i stad wziął się błąd z drugeigo screena.

Rozwiązaniem jest zapewnienie wykonywania add() tylko przez jeden wątek. Dopiero gdy jeden wątek skończy wykonywać add(), drugi będzie mógł dostać się do listy. Robimy to za pomocą wprowadzenia bloku zsynchronizowanego (nazywanego też sekcją krytyczną).

Teraz nawet przy uruchomieniu 4 wątków zawsze dostanę prawidłowe wyniki.

Wyjaśnimy sobie składnię sekcji krytycznej i jej działanie. Blok synchronizowany tworzymy za pomocą słowa synchronized, a w nawiasach podajemy obiekt, do którego odwołania chcemy synchronizować. Po drugie sekcja krytyczna może być wykonywana tylko przez jeden wątek naraz (reszta musi czekać). Ostatnią rzeczą jest to, że synchronizować możemy tylko obiekty, i powinne one być stałymi, jeśli zmienimy wartość referencji do obiektu np. wpisując do zmiennej list nową listę, synchronizacja nie zadziała. I ostatnia uwaga - obiekt synchronizowany nie może być null

Synchronizowanie obiektów, za rzecz których wywołano metodę

Nasz kod jest brzydki. Synchronizacja jest wykonywana tylko w przypadku uruchomienia wątków z konkretnym Runnable'm. Stworzymy teraz prostą implementację ArrayList, tak aby za pomocą polimorfizmu programista nie musiał się przejmować synchronizacją.

Dodanie synchronized do deklaracji metody powoduje objęcie blokiem zsynchronizowanym całego jej ciała, synchronizującego odwołania do obiektu, na rzecz którego zstała wywołana. Jest to zapis równoważny z:

Teraz mogę już wyrzucić bloki synchronizowane z Runnable'a, a kod dalej będzie wykonywał się prawidłowo:

Zamki obiektowe - czyli co dzieje się pod synchronized, intrukcja try-finally

Blok synchronized jest równoważny z poniższym zapisem:

Komenda lock() blokuje obiekt dla innych wątków oprócz tego, który ją wywołał (czyli dokładnie to samo co synchronized). Konieczne jest tutaj użycie bloku try-finally, ponieważ bezwzględnie musimy zwolnić blokadę. Nie wspominając już o możliwości wystąpienia wyjątków w sekcji try{}, to może pojawić się w niej instrukcja return, która przerwie wykonywanie metody. Jeśli natomiast odblokowanie obiektu umieścimy w sekcji finally{} to obiekt zawsze zostanie zwolniony niezależnie czy w trakcie wykonania nastąpiły wyjątki, czy w bloku try{} zwracamy jakąś wartość.