Download as pdf or txt
Download as pdf or txt
You are on page 1of 26

Java.

Wspbieno
dla praktykw
Autor: Zesp autorw
Tumaczenie: Rafa Joca
ISBN: 978-83-246-0921-5
Tytu oryginau: Java Concurrency in Practice
Format: B5, stron: 376

Twrz bezpieczne i wydajne aplikacje wielowtkowe


Chcesz podnie wydajno swoich aplikacji? Planujesz stworzenie systemu, ktry
bdzie uruchamiany na maszynach wyposaonych w procesory wielordzeniowe?
A moe prbowae ju tworzy aplikacje wielowtkowe, ale zniechcie si po
wielogodzinnych poszukiwaniach przyczyn bdw, ktre pojawiaj si przy wysokich
obcieniach? Java niemal od pocztku swego istnienia jest wyposaona w mechanizmy
umoliwiajce tworzenie aplikacji wielowtkowych, lecz dopiero wersja 5. wniosa
zupenie now jako, dziki wielu poprawkom zwikszajcym wydajno maszyny
wirtualnej oraz dodatkowym klasom uatwiajcym osiganie lepszej wspbienoci.
W ksice Java. Wspbieno dla praktykw znajdziesz wyczerpujcy opis metod
projektowania i tworzenia aplikacji wielowtkowych. Przeczytasz nie tylko o klasach,
ich dziaaniu i sposobach wykorzystania, ale rwnie poznasz wzorce projektowe,
praktyki programistyczne i modele, dziki ktrym programowanie wspbiene jest
atwiejsze. Znajdziesz tu praktyczne aspekty oraz przykady tworzenia pewnych,
skalowalnych i atwych w konserwacji aplikacji wspbienych. Dowiesz si take,
w jaki sposb testowa aplikacje wielowtkowe, wynajdywa w nich bdy i usuwa je.
Dziki ksice poznasz:

Wydawnictwo Helion
ul. Kociuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl

Moliwoci wykorzystania wtkw


Podstawy stosowania wtkw
Wspdzielenie obiektw
Struktura aplikacji wielowtkowych
Zarzdzanie wtkami i zadaniami
Zastosowania pul wtkw
Optymalizowanie wydajnoci
Skalowalno aplikacji wielowtkowych
Testowanie aplikacji wspbienych
Model pamici Javy
Tworzenie wasnych synchronizatorw

Przedmowa ...................................................................................... 9
Rozdzia 1. Wprowadzenie ................................................................................ 13
1.1. (Bardzo) krtka historia wspbienoci .................................................................. 13
1.2. Zalety wtkw .......................................................................................................... 15
1.3. Ryzyka zwizane z wtkami ..................................................................................... 18
1.4. Wtki s wszdzie .................................................................................................... 21

Cz I

Podstawy ..................................................................... 25

Rozdzia 2. Wtki i bezpieczestwo .................................................................. 27


2.1. Czym jest bezpieczestwo wtkowe? ....................................................................... 29
2.2. Niepodzielno ......................................................................................................... 31
2.3. Blokady ..................................................................................................................... 35
2.4. Ochrona stanu za pomoc blokad ............................................................................. 39
2.5. ywotno i wydajno ............................................................................................ 41

Rozdzia 3. Wspdzielenie obiektw ................................................................ 45


3.1. Widoczno .............................................................................................................. 45
3.2. Publikacja i ucieczka ................................................................................................ 51
3.3. Odosobnienie w wtku ............................................................................................. 54
3.4. Niezmienno ........................................................................................................... 58
3.5. Bezpieczna publikacja .............................................................................................. 61

Rozdzia 4. Kompozycja obiektw ..................................................................... 67


4.1. Projektowanie klasy bezpiecznej wtkowo .............................................................. 67
4.2. Odosobnienie egzemplarza ....................................................................................... 71
4.3. Delegacja bezpieczestwa wtkowego ..................................................................... 76
4.4. Dodawanie funkcjonalnoci do istniejcych klas bezpiecznych wtkowo .............. 82
4.5. Dokumentowanie strategii synchronizacji ................................................................ 86

Rozdzia 5. Bloki budowania aplikacji ............................................................... 89


5.1. Kolekcje synchronizowane ....................................................................................... 89
5.2. Kolekcje wspbiene ............................................................................................... 94
5.3. Kolejki blokujce oraz wzorzec producenta i konsumenta ...................................... 97
5.4. Metody blokujce i przerywane ............................................................................. 102
5.5. Synchronizatory ...................................................................................................... 104
5.6. Tworzenie wydajnego, skalowalnego bufora wynikw ......................................... 112
Podsumowanie czci I .................................................................................................. 117

Cz II

Java. Wspbieno dla praktykw

Struktura aplikacji wspbienej .................................. 119

Rozdzia 6. Wykonywanie zada ..................................................................... 121


6.1. Wykonywanie zada w wtkach ............................................................................ 121
6.2. Szkielet Executor .................................................................................................... 125
6.3. Znajdowanie sensownego zrwnoleglenia ............................................................. 132
Podsumowanie ............................................................................................................... 141

Rozdzia 7. Anulowanie i wyczanie zada ..................................................... 143


7.1. Anulowanie zada .................................................................................................. 144
7.2. Zatrzymanie usugi wykorzystujcej wtki ............................................................ 158
7.3. Obsuga nietypowego zakoczenia wtku .............................................................. 167
7.4. Wyczanie maszyny wirtualnej ............................................................................. 170
Podsumowanie ............................................................................................................... 173

Rozdzia 8. Zastosowania pul wtkw ............................................................ 175


8.1. Niejawnie splecione zadania i strategie wykonania ............................................... 175
8.2. Okrelanie rozmiaru puli wtkw ........................................................................... 178
8.3. Konfiguracja klasy ThreadPoolExecutor ................................................................ 179
8.4. Rozszerzanie klasy ThreadPoolExecutor ............................................................... 187
8.5. Zrwnoleglenie algorytmw rekurencyjnych ......................................................... 188
Podsumowanie ............................................................................................................... 195

Rozdzia 9. Aplikacje z graficznym interfejsem uytkownika ............................. 197


9.1. Dlaczego graficzne interfejsy uytkownika s jednowtkowe? ............................. 197
9.2. Krtkie zadanie interfejsu graficznego ............................................................... 201
9.3. Dugie czasowo zadania interfejsu graficznego ..................................................... 203
9.4. Wspdzielone modele danych ............................................................................... 208
9.5. Inne postacie podsystemw jednowtkowych ...................................................... 209
Podsumowanie ............................................................................................................... 210

Cz III ywotno, wydajno i testowanie ............................ 211


Rozdzia 10. Unikanie hazardu ywotnoci ........................................................ 213
10.1. Blokada wzajemna ................................................................................................ 213
10.2. Unikanie i diagnostyka blokad wzajemnych ........................................................ 223
Podsumowanie ............................................................................................................... 228

Rozdzia 11. Wydajno i skalowalno ............................................................ 229


11.1. Mylenie na temat wydajnoci ............................................................................. 229
11.2. Prawo Amdahla .................................................................................................... 233
11.3. Koszta wprowadzane przez wtki ........................................................................ 237
11.4. Zmniejszanie rywalizacji o blokad ..................................................................... 240
11.5. Przykad porwnanie wydajnoci obiektw Map ............................................ 250
11.6. Redukcja narzutu przeczania kontekstu ............................................................. 251
Podsumowanie ............................................................................................................... 253

Rozdzia 12. Testowanie programw wspbienych .......................................... 255


12.1. Testy sprawdzajce poprawno .......................................................................... 256
12.2. Testowanie wydajnoci ......................................................................................... 268
12.3. Unikanie pomyek w testach wydajnoci ............................................................. 273
12.4. Testy uzupeniajce .............................................................................................. 278
Podsumowanie ............................................................................................................... 281

Spis treci

Cz IV Techniki zaawansowane ............................................. 283


Rozdzia 13. Blokady jawne ............................................................................. 285
13.1. Interfejs Lock i klasa ReentrantLock .................................................................... 285
13.2. Rozwaania na temat wydajnoci ......................................................................... 290
13.3. Uczciwo ............................................................................................................. 291
13.4. Wybr midzy synchronized i ReentrantLock ..................................................... 293
13.5. Blokady odczyt-zapis ........................................................................................... 294
Podsumowanie ............................................................................................................... 297

Rozdzia 14. Tworzenie wasnych synchronizatorw .......................................... 299


14.1. Zarzdzanie zalenoci od stanu ......................................................................... 299
14.2. Wykorzystanie kolejek warunkw ....................................................................... 306
14.3. Jawne obiekty warunkw ..................................................................................... 314
14.4. Anatomia synchronizatora .................................................................................... 316
14.5. Klasa AbstractQueuedSynchronizer ........................................................................ 318
14.6. AQS w klasach synchronizatorw pakietu java.util.concurrent ........................... 321
Podsumowanie ............................................................................................................... 324

Rozdzia 15. Zmienne niepodzielne i synchronizacja nieblokujca ...................... 325


15.1. Wady blokowania ................................................................................................. 326
15.2. Sprztowa obsuga wspbienoci ...................................................................... 327
15.3. Klasy zmiennych niepodzielnych ......................................................................... 331
15.4. Algorytmy nieblokujce ....................................................................................... 335
Podsumowanie ............................................................................................................... 342

Rozdzia 16. Model pamici Javy ...................................................................... 343


16.1. Czym jest model pamici i dlaczego ma mnie interesowa? ............................... 343
16.2. Publikacja ............................................................................................................. 350
16.3. Bezpieczestwo inicjalizacji ................................................................................. 355
Podsumowanie ............................................................................................................... 356

Dodatki ...................................................................... 357


Dodatek A Adnotacje zwizane ze wspbienoci ........................................ 359
A.1. Adnotacje dla klas .................................................................................................. 359
A.2. Adnotacje pl i metod ............................................................................................ 360

Dodatek B Bibliografia .................................................................................. 361


Skorowidz .................................................................................... 365

Rozdzia 3.

Na pocztku rozdziau 2. pojawio si stwierdzenie, e poprawne programy wspbiene


musz przede wszystkim waciwie zarzdza dostpem do wspdzielonego, zmiennego stanu. Tamten rozdzia dotyczy uycia synchronizacji do zabezpieczenia si przed
wieloma wtkami korzystajcymi z tych samych danych w tym samym momencie.
Ten prezentuje techniki wspdzielenia i publikacji obiektw, by byy bezpieczne do
stosowania w wielu wtkach. Razem oba elementy stanowi podstaw tworzenia klas
bezpiecznych wtkowo i poprawnej konstrukcji wspbienych aplikacji za pomoc
klas biblioteki java.util.concurrent.
W poprzednim rozdziale przedstawilimy, w jaki sposb bloki i metody synchronized
zapewniaj niepodzielno operacji. Wielu osobom wydaje si, e te bloki dotycz
tylko niepodzielnoci i oznaczania sekcji krytycznych. Synchronizacja ma take inny
istotny, cho subtelny, aspekt widoczno pamici. Chcemy nie tylko zapewni,
by gdy jeden wtek modyfikuje stan obiektu, inne mu w tym nie przeszkadzay, ale
i to, by inne wtki rzeczywicie widziay dokonan zmian. Nie mona tego osign
bez synchronizacji. Obiekty s bezpiecznie publikowane czy to za pomoc jawnej
synchronizacji, czy przez zastosowanie synchronizacji wybudowanej w klasy biblioteki.

3.1. Widoczno
Widoczno to subtelny temat, bo zadania, ktre mog si nie uda, s mao intuicyjne.
W rodowisku jednowtkowym, gdy zapisujemy warto do zmiennej i pniej j odczytujemy (w midzyczasie nie byo innych zapisw), moemy si spodziewa otrzymania tej samej wartoci. Wydaje si to w miar naturalne. Z tego wzgldu pocztkowo
trudno zaakceptowa, e w przypadku wielu odczytw i zapisw z wielu wtkw
przedstawione zaoenie moe nie zaistnie. Oglnie nie ma gwarancji, i wtek
odczytujcy zobaczy warto zapisan przez inny wtek w odpowiednim czasie, a nawet
w ogle. Aby zapewni widoczno zapisw do pamici w rnych wtkach, naley
uy synchronizacji.

Cz I Podstawy

46

Listing 3.1 przedstawia klas NoVisibility wskazujc, co moe pj nie tak, jeli
wtki wspdziel dane bez synchronizacji. Dwa wtki, gwny i odczytujcy, korzystaj
ze wspdzielonych zmiennych ready i number. Gwny wtek uruchamia wtek odczytujcy, a nastpnie ustawia number na 42 i ready na true. Wtek odczytujcy czeka,
a ready bdzie rwne true, i dopiero wtedy wywietla warto number. Cho wydawaoby si oczywiste, e NoVisibility zawsze wywietli 42, w praktyce moe wywietli
0 lub w ogle nie wyj z ptli! Z powodu braku odpowiedniej synchronizacji nie mamy
adnej gwarancji, e wartoci ready i number zapisane przez gwny wtek zobaczy
wtek odczytujcy.
Listing 3.1. Wspdzielenie zmiennych bez synchronizacji. Nie rb tak
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready)
Thread.yield();
System.out.println(number);
}
}

public static void main(String[] args) {


new ReaderThread().start();
number = 42;
ready = true;
}

Kod klasy NoVisibility moe przebywa w ptli nieskoczenie dugo, bo warto ready
moe nigdy nie zosta zauwaona przez wtek odczytujcy. Co jeszcze dziwniejsze,
kod moe wywietli warto 0, gdy zapis ready bdzie widoczny wczeniej ni zapis
number (tak zwana zmiana kolejnoci). Nie ma gwarancji, e operacje jednego wtku
wykonaj si w kolejnoci podanej przez program, jeli tylko zmiana kolejnoci bdzie
niezauwaalna przez wtek dokonujcy modyfikacji nawet jeli oznacza to zmian
kolejnoci zmian w innych wtkach1. Cho gwny wtek w kodzie rdowym
najpierw zapisuje number, a pniej ready, bez synchronizacji inny wtek moe zauway
te operacje w odwrotnej kolejnoci (lub nawet wcale ich nie widzie).
Przy braku synchronizacji kompilator, procesor i system wykonawczy mog wykonywa
dziwne przemeblowania operacji, ktre maj wykona. Prby wczeniejszego logicznego wskazywania kolejnoci wykonania w pamici okrelonych dziaa przy
braku synchronizacji wielowtkowej niemal na pewno bd niepoprawne.

Mogoby to wskazywa na ze zaprojektowanie systemu, ale tak naprawd wynika to z faktu


wykorzystywania przez maszyn wirtualn penej wydajnoci nowoczesnych systemw
wieloprocesorowych. Przy braku synchronizacji model pamici Javy dopuszcza, by kompilator
zmieni kolejno operacji i buforowa wartoci w rejestrach. Dopuszcza te zmian kolejnoci
wykonania dziaa przez procesor i jego bufory. Wicej informacji na ten temat znajduje
si w rozdziale 16.

Rozdzia 3. Wspdzielenie obiektw

47

Klasa NoVisibility to chyba najprostsza posta programu wspbienego dwa wtki


i dwie wspdzielone zmienne a mimo to zbyt atwo wysnu ze wnioski co do jego
dziaania i sposobu opuszczenia ptli. Wysnucie odpowiednich wnioskw co do kolejnoci dziaa w niepoprawnie zsynchronizowanym programie wielowtkowym jest
wrcz niemoliwe.
Wszystko to brzmi gronie i rzeczywicie takie jest. Na szczcie istnieje prosty rodek
zaradczy kadorazowe uycie odpowiedniej synchronizacji, gdy tylko dane s
wspdzielone przez wiele wtkw.

3.1.1. Niewiee dane


Klasa NoVisibility przedstawia jeden z powodw zwracania przez niepoprawnie
zsynchronizowane programy zadziwiajcych wynikw niewieo danych. Gdy
wtek odczytujcy testuje warto ready, moe widzie przedatowan warto. Jeli
synchronizacji nie stosuje si przy kadym dostpie do zmiennej, mona uzyska
niewiey odczyt. Co gorsza, taki odczyt nie odbywa si na zasadzie wszystko albo
nic wtek jedn zmienn odczyta aktualn, a drug niewan (nawet jeli teoretycznie
zostaa zapisana jako pierwsza).
Czasem niewiee jedzenie mona spoy jest to tylko mniej przyjemne. Niewiee
dane bywaj bardzo grone. Cho nieaktualny licznik odwiedzin aplikacji internetowej
raczej nikomu nie zaszkodzi2, tak inna niewiea warto moe doprowadzi do powanych bdw. W klasie NoVisibility nieaktualne dane prowadz czasem do wywietlenia
zego wyniku, a nawet do zablokowania jednego z wtkw. Sprawa komplikuje si
jeszcze bardziej, gdy niewieo dotyczy referencji do obiektw, na przykad czy
listy jednokierunkowej. Niewiee dane mog powodowa powane i tajemnicze
pomyki, jak niespodziewane wyjtki, bdy struktury danych, niedokadne obliczenia oraz ptle nieskoczone.
Klasa MutableInteger z listingu 3.2 nie jest bezpieczna wtkowo, bo z pola value korzystaj metody get() i set() bez synchronizacji. Poza innymi hazardami, klasa jest
naraona na niewieo danych: jeli jeden wtek wywouje set(), drugi wtek wywoujcy get() moe nie widzie aktualizacji.
Listing 3.2. Niezabezpieczona przed wtkami klasa przechowujca liczb cakowit
@NotThreadSafe
public class MutableInteger {
private int value;

}
2

public int get() { return value; }


public void set(int value) { this.value = value; }

Odczyt danych bez synchronizacji przypomina uycie poziomu izolacji READ_UNCOMMITED


(odczyt niezatwierdzony) w bazie danych, gdy staramy si zwikszy wydajno kosztem dokadnoci.
Odczyt niesynchronizowany to co wicej ni tylko utrata dokadnoci, bo widoczna warto
wspdzielonej zmiennej moe by naprawd powanie przedatowana.

Cz I Podstawy

48

Bezpieczestwo klasie MutableInteger zapewnimy, synchronizujc metod ustawiajc


i pobierajc. Now wersj przedstawia klasa SynchronizedInteger z listingu 3.3.
Synchronizacja tylko metody ustawiajcej nie wystarcza w takiej sytuacji wtek
pobierajcy warto nadal byby naraony na niewieo.
Listing 3.3. Zabezpieczona przed wtkami klasa przechowujca liczb cakowit
@ThreadSafe
public class SynchronizedInteger {
@GuardedBy("this") private int value;

public synchronized int get() { return value; }


public synchronized void set(int value) { this.value = value; }

3.1.2. Niepodzielne operacje 64-bitowe


Gdy wtek odczytuje zmienn bez synchronizacji, moe widzie niewie warto,
ale przynajmniej jest to warto umieszczona tam przez inny wtek, a nie jaka losowa
warto. Mwi si w takiej sytuacji o bezpieczestwie poprawnoci.
To bezpieczestwo dotyczy wszystkich zmiennych poza jednym wyjtkiem 64-bitowych zmiennych liczbowych (double i long) niezadeklarowanych jako volatile
(patrz punkt 3.1.4). Model pamici Javy wymaga, by operacje pobrania i zapamitania
byy niepodzielne, ale dla nieulotnych zmiennych long i double maszyna wirtualna
moe potraktowa odczyt lub zapis 64-bitowy jako dwie operacje 32-bitowe. Jeli zapis
i odczyt takiej nieulotnej zmiennej odbywa si w dwch rnych wtkach, wtek
odczytujcy moe przeczyta nisze 32 bity nowej wartoci i wysze 32 bity starej3.
Nawet jeli kto nie przejmuje si niewieymi danymi, powinien uwaa na wspdzielenie zmiennych typu long lub double w programach wielowtkowych, jeli nie s
one zadeklarowane jako volatile lub chronione blokad.

3.1.3. Blokowanie i widoczno


Blokada wewntrzna suy do zagwarantowania, e jeden wtek zauway zmian wykonan przez inny wtek w sposb przewidywalny, co przedstawia rysunek 3.1. Gdy
wtek A wykonuje blok synchronized i w tym czasie wtek B prbuje uzyska dostp
do tego bloku chronionego t sam blokad, wartoci zmiennych widoczne dla A przed
zwolnieniem blokady bd rwnie widoczne dla B, gdy uzyska wreszcie dostp do
bloku. Innymi sowy, wszystko, co A wykonao w synchronizowanym bloku, bdzie
widoczne dla B, gdy wykona blok chroniony t sam blokad. Bez synchronizacji
nie ma takiej gwarancji.

W czasie powstawania specyfikacji maszyny wirtualnej niewiele architektur procesorw mogo


wydajnie obsuy niepodzielne 64-bitowe operacje arytmetyczne.

Rozdzia 3. Wspdzielenie obiektw

49

Rysunek 3.1.
Widoczno
gwarantowana przez
synchronizacj

Moemy doda kolejn regu wymagajc synchronizacji wszystkich wtkw t sam


blokad w momencie dostpu do wspdzielonej zmiennej gwarancj widocznoci
zmiany wykonanej przez jeden wtek dla wszystkich innych wtkw. Jeli wtek odczytuje warto zmiennej bez synchronizacji, moe otrzyma niewiee dane.
Blokowanie nie dotyczy wycznie wzajemnego wykluczenia wykonywania, ale rwnie
widocznoci pamici. Aby zapewni, e wszystkie wtki widz najnowsze wersje informacji we wspdzielonych zmiennych, wtki odczytujce i zapisujce musz synchronizowa si za pomoc tej samej blokady.

3.1.4. Zmienne ulotne


Jzyk Javy zawiera dodatkowo sabsz wersj synchronizacji w postaci zmiennych
ulotnych, ktra zapewnia przewidywalny sposb propagacji aktualizacji do innych
wtkw. Pole z modyfikatorem volatile wskazuje kompilatorowi i systemowi wykonawczemu, e zmienna jest wspdzielona i wykonywanych na niej w pamici operacji
nie naley ukada w innej kolejnoci ni wskazana w kodzie rdowym. Zmienne
ulotne nie s przechowywane w rejestrach ani w buforach, gdy s ukryte przed innymi
procesorami. W ten sposb odczyt w dowolnym momencie zawsze zwraca najnowsz
wersj zmiennej zapisan przez dowolny wtek.
Warto zmienne ulotne traktowa w taki sposb, jakby zachowyway si jak klasa SynchronizedInteger z listingu 3.3, w ktrej wywoania get() i set() zastpiono odczytami

Cz I Podstawy

50

i zapisami4. Dostp do zmiennej ulotnej nie zakada adnych blokad, wic nie blokuje adnego wtku. W ten sposb zmienne ulotne nie generuj takiego narzutu jak
peny mechanizm synchronizacji5.
Efekt widocznoci zmiennych ulotnych wykracza poza warto samej zmiennej ulotnej.
Gdy wtek A zapisze dane w zmiennej ulotnej, a wtek B odczyta t sam zmienn,
wartoci wszystkich zmiennych, ktre byy widoczne w A przed zapisaniem zmiennej
ulotnej, bd widoczne w B po dokonaniu odczytu zmiennej ulotnej. Z punktu widzenia
widocznoci pamici zapis zmiennej ulotnej przypomina wyjcie z bloku synchronizujcego. Nie radzimy zbyt mocno polega na zmiennych ulotnych w kwestii widocznoci. Kod, ktry korzysta z tego rozwizania, trudniej zrozumie i testowa ni kod
jawnie stosujcy blokady.
Uywaj zmiennych ulotnych tylko wtedy, gdy upraszczaj implementacj i weryfikacj
strategii synchronizacji. Unikaj ich stosowania, gdy weryfikacja wymagaaby dokadnej
analizy przypadku. Dobre uycia zmiennych ulotnych dotycz zapewnienia widocznoci
ich wasnego stanu, obiektu, do ktrego si odnosz, lub wystpienia istotnego
zdarzenia (na przykad inicjalizacji lub wyczania systemu).

Listing 3.4 ilustruje typowy przykad uycia zmiennych ulotnych sprawdzenie znacznika stanu, by wykry potrzeb opuszczenia ptli. W przykadzie wtek stara si zasn
z uyciem metody zliczajcej wirtualne owce. Aby przykad dziaa poprawnie, zmienna
asleep musi by ulotna. W przeciwnym razie wtek mgby nie zauway ustawienia
zmiennej asleep przez inny wtek6. Nic nie stoi na przeszkodzie, by w tym miejscu
uy blokady zapewniajcej widoczno, ale uczyniaby ona kod mniej przejrzystym.
Listing 3.4. Zliczanie owiec
volatile boolean asleep;
...
while (!asleep)
countSomeSheep();

Zmienne ulotne s wygodne, ale maj swoje ograniczenia. Najczciej stosuje si je


jako znaczniki zakoczenia, przerwania lub statusu (patrz listing 3.4). Mona ich uy
rwnie do innych rodzajw informacji o stanie, ale wtedy naley bardziej uwaa.
4

Nie jest to dokadna analogia. Efekt widocznoci pamiciowej klasy SynchronizedInteger jest
mocniejszy od zmiennych ulotnych. Szczegy w rozdziale 16..
Odczyty ulotne s tylko odrobin wolniejsze od zwykych odczytw w wikszoci nowoczesnych
architektur procesorw.
Uwaga dla testujcych: dla aplikacji serwerowych zawsze wczaj opcj -server maszyny wirtualnej,
nawet w trakcie implementacji i testowania. Maszyna wirtualna w wersji serwerowej przeprowadza
wicej optymalizacji ni wersja kliencka, na przykad przez wyrzucanie zmiennych poza ptl,
jeli nie s w niej modyfikowane. Kod mogcy dziaa poprawnie w wersji klienckiej (w trakcie testw)
przestanie dziaa na serwerze produkcyjnym (wersja serwerowa). Przypumy, e zapomnielimy
zadeklarowa zmiennej asleep jako volatile z listingu 3.4. Wersja serwerowa usunie test z ptli
(powstanie ptla nieskoczona), ale tego kroku nie uczyni wersja kliencka. Ptla nieskoczona wystpujca
w trakcie testw jest mniej kosztowna od tej pojawiajcej si tylko w wersji produkcyjnej.

Rozdzia 3. Wspdzielenie obiektw

51

Przykadowo semantyka modyfikatora volatile nie jest na tyle silna, by zagwarantowa


niepodzielno operacji inkrementacji (count++), chyba e mona zagwarantowa zapis
zmiennej tylko przez jeden wtek. Zmienne niepodzielne zapewniaj nierozczn obsug operacji odczyt, modyfikacja, zapis, wic czsto mona je stosowa jako lepsze
wersje zmiennych ulotnych; wicej informacji na ten temat w rozdziale 15.).
Blokada gwarantuje widoczno i niepodzielno. Zmienna ulotna moe zagwarantowa co najwyej widoczno.

Zmienne ulotne stosuj tylko wtedy, gdy spenione s wszystkie ponisze kryteria:
t zapis zmiennej nie zaley od jej aktualnej wartoci lub gdy mona zapewni

zapis aktualizacji tylko przez jeden wtek,


t zmienna nie jest skadow niezmiennika obejmujcego inne zmienne stanowe,
t blokowanie nie jest potrzebne z innych powodw w trakcie dostpu do zmiennej.

3.2. Publikacja i ucieczka


Publikacja obiektu oznacza jego udostpnienie kodowi spoza jego aktualnego zasigu,
na przykad przez zapamitanie referencji do niego, by mg z niego skorzysta inny
kod, zwrcenie go z nieprywatnej metody lub przekazanie jako argument metody innej
klasy. W wikszoci sytuacji chcemy mie pewno, e obiekty i ich wewntrzne dane
nie s publikowane. W innych przypadkach publikujemy obiekt w celu oglnego
uycia wykonanie tego zadania w sposb bezpieczny wtkowo wymaga synchronizacji. Publikacja zmiennych z wewntrznym stanem obiektu szkodzi hermetyzacji.
Publikacja obiektu przed ich cakowitym utworzeniem zmniejsza bezpieczestwo
aplikacji. Publikacj obiektu, gdy nie powinno to mie miejsca, nazywa si ucieczk
lub wyciekiem. Podrozdzia 3.5 omawia idiomy bezpiecznej publikacji. Na razie
przyjrzymy si sposobom ucieczki obiektu.
Najbardziej ewidentna forma publikacji polega na zapamitaniu referencji w publicznym
i statycznym polu, z ktrego moe skorzysta dowolna klasa lub wtek (patrz listing 3.5).
Metoda initialize() inicjalizuje nowy obiekt HashSet i publikuje go w zmiennej
knownSecrets.
Listing 3.5. Publikacja obiektu
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}

Publikacja jednego obiektu moe porednio opublikowa inne. Dodanie obiektu Secret
do knownSecrets spowoduje publikacj tego obiektu, bo kady kod moe przej przez
zbir i uzyska referencj do Secret. Zwrcenie referencji z nieprywatnej metody

Cz I Podstawy

52

rwnie publikuje zwrcony obiekt. Klasa UnsafeStates z listingu 3.6 publikuje tablic
skrtw stanw, ktra by moe powinna pozosta prywatna.
Listing 3.6. Umoliwia ucieczk wewntrznego, zmiennego stanu. Nie rb tak
class UnsafeStates {
private String[] states = new String[]{
"A " "A " ...
};
}

public String[] getStates() { return states; }

Publikacja states w ten sposb jest problematyczna, bo dowolny kod moe zmieni
jej zawarto. W przedstawionej sytuacji tablica states ucieka z jej domylnego zakresu,
bo to, co miao pozosta prywatne, tak naprawd zostao upublicznione.
Publikacja obiektw publikuje wszystkie inne obiekty znajdujce si w jego nieprywatnych polach. Bardziej oglnie: dowolny obiekt osigalny z poziomu opublikowanego
obiektu przez acuch nieprywatnych referencji i wywoa metod rwnie zosta
opublikowany.
Z perspektywy klasy C metoda obca to taka, ktrej zachowanie nie zostao w peni
okrelone w C. Dotyczy to metod w innych klasach oraz metod przysanianych (rnych
od private i final) w samej klasie C. Przekazanie obiektu do metody obcej naley
rwnie traktowa jako publikacj tego obiektu. Nie wiemy tak naprawd, jak zachowa
si zewntrzny kod nie musi, ale moe przekaza opublikowany obiekt innym obiektom, by moe dziaajcym w obrbie innych wtkw.
Co tak naprawd inny wtek zrobi z opublikowan referencj do obiektu, nie ma znaczenia, bo cay czas istnieje ryzyko jego niepoprawnego uycia7. Ucieczka obiektu
oznacza, e musimy zaoy, i inna klasa lub wtek specjalnie lub przypadkowo le
go uyje. To bardzo wany powd przemawiajcy za hermetyzacj, bo uatwia analiz
programw pod ktem poprawnoci i utrudnia przypadkowe zamanie ogranicze
projektowych.
Ostatnim mechanizmem, dziki ktremu obiekt lub jego wewntrzny stan mog zosta
opublikowane, jest publikacja egzemplarza klasy wewntrznej. Przedstawia to klasa
ThisTscape z listingu 3.7. Gdy ThisTscape publikuje obiekt TventListener, niejawnie
publikuje rwnie egzemplarz ThisTscape, bo egzemplarz klasy wewntrznej zawiera
ukryt referencj do swego zewntrznego kolegi.
Listing 3.7. Niejawna ucieczka referencji this. Nie rb tak
public class ThisEscape {
public ThisEscape(EventSource source) {
source.register istener(new Event istener() {
7

Jeli kto ukradnie Twoje haso i umieci je na licie dyskusyjnej alt.free-passwords, informacja ta
po prostu wycieka. Nie ma znaczenia, czy kto rzeczywicie uy tego hasa w celu oszustwa.
Publikacja referencji stwarza podobne ryzyko.

Rozdzia 3. Wspdzielenie obiektw

});

53

public void onEvent(Event e) {


doSomething(e);
}

3.2.1. Tworzenie bezpiecznych konstrukcji


Klasa ThisTscape ilustruje bardzo wany szczeglny przypadek ucieczki, bo w trakcie
konstrukcji klasy ucieka referencja this. Opublikowanie wewntrznego egzemplarza
TventListener publikuje rwnie egzemplarz ThisTscape. Obiekt znajdzie si w przewidywalnym, spjnym stanie dopiero po powrocie z konstruktora, wic ucieczka ju
we wntrzu konstruktora moe spowodowa publikacj niedokoczonego obiektu.
Sytuacja obejmuje rwnie przypadek, w ktrym publikacja to ostatnia instrukcja
konstruktora. Jeli referencja this wycieknie w trakcie konstrukcji obiektu, obiekt
traktuje si jako niepoprawnie skonstruowany8.
Nie dopuszczaj do konstrukcji ucieczki referencji this w konstruktorze obiektu.

Typowym bdem umoliwiajcym ucieczk referencji this z konstruktora jest uruchamianie wtku we wntrzu konstruktora. Gdy obiekt tworzy wtek w swym konstruktorze,
niemal zawsze wspdzieli z nim referencj this, czy to jawnie (przez przekazanie jej
do konstruktora), czy niejawnie (poniewa Thread i Runnable to klasy wewntrzne
obiektu). Nowy wtek moe widzie obiekt, ktrego cz stanowi, przed pen inicjalizacj. W zasadzie nie ma nic zego w utworzeniu wtku w konstruktorze po
prostu nie naley go od razu uruchamia. Zamiast tego wystarczy udostpni metod
start() lub initialize() uruchamiajc wewntrzny wtek. Wicej informacji na
temat cyklu ycia usug znajduje si w rozdziale 7. Wywoanie przesonitej metody
egzemplarza (czyli takiej, ktra nie jest prywatna ani ostateczna) z poziomu konstruktora rwnie dopuszcza wyciek referencji this.
Gdy prbuje si zarejestrowa nasuchiwanie zdarze lub uruchomi wtek w konstruktorze, warto zapewni poprawn konstrukcj, uywajc prywatnego konstruktora
i publicznej metody fabrycznej, co przedstawia klasa SafeListener z listingu 3.8.
Listing 3.8. Uycie metody fabrycznej w celu uniknicia wycieku referencji this w trakcie konstrukcji
obiektu
public class Safe istener {
private final Event istener listener;
private Safe istener() {
listener = new Event istener() {
8

Ucilijmy: referencja this nie moe wyciec z wtku przed powrotem z konstruktora. Referencj this
mona przechowywa w innym miejscu, jeli tylko nie zostanie wykorzystana przez inny wtek przed
zakoczeniem konstrukcji. Listing 3.8 przedstawia poprawion wersj.

Cz I Podstawy

54

};

public void onEvent(Event e) {


doSomething(e);
}

public static Safe istener newInstance(EventSource source) {


Safe istener safe = new Safe istener();
source.register istener(safe.listener);
return safe;
}

3.3. Odosobnienie w wtku


Dostp do wspdzielonych zasobw wymaga synchronizacji. Jednym ze sposobw
uniknicia tego problemu jest brak wspdzielenia. Jeli dane s dostpne tylko z poziomu
jednego wtku, nie potrzeba synchronizacji. Technika ta, zwana odosabnianiem wtku,
jest jednym z najprostszych sposobw osignicia bezpieczestwa pod ktem wtkw.
Jeli obiekt jest ograniczony do jednego wtku, jego uycie jest bezpieczne, nawet
jeli sam obiekt nie zawiera adnych zabezpiecze [CJP 2.3.2].
Swing intensywnie korzysta z odosabniania wtku. Komponenty wizualne i modele
danych Swing nie s zabezpieczone pod ktem wtkw. Ich bezpieczestwo zapewnia
specjalny wtek rozdzielania zada. Aby poprawnie korzysta ze Swinga, kod uruchamiany z wtkw innych ni wtek rozdzielajcy nie powinien mie dostpu do tych
obiektw. By uatwi to zadanie, Swing dostarcza mechanizm invokeLater, ktry
harmonogramuje obiekt Runnable w celu jego wykonania w wtku rozdzielajcym.
Wiele bdw wspbienoci w aplikacjach Swing wynika wanie z niepoprawnego
uycia tych ukrytych obiektw z innego wtku.
Innym popularnym zastosowaniem odosabniania wtkw jest uycie puli obiektw
Connection z JDBC. Specyfikacja JDBC nie wymaga, by obiekty Connection byy
bezpieczne wtkowo9. W typowej aplikacji serwerowej wtek pobiera poczenie z puli,
uywa go do wykonania pojedynczego zapytania i zwraca obiekt do puli. Poniewa
wikszo da, wywoania serwletw i EJB, przetwarzana jest synchronicznie przez
jeden wtek i pula nie przekae tego samego poczenia innemu wtkowi, dopki
pierwszy go nie zwrci, wzorzec ten zapewnia niejawne odosobnienie obiektu Connection
tylko do jednego wtku na czas wykonania dania.
Podobnie jak jzyk nie zawiera mechanizmu wymuszajcego stosowanie blokady do
zabezpieczania zmiennych, tak nie ma adnego konkretnego sposobu ograniczenia
stosowania obiektu tylko do jednego wtku. Odosobnienie w wtku to element projektu
programu, ktry musi wymusi sama jego implementacja. Jzyk i gwne biblioteki
9

Pule pocze udostpniane przez serwery aplikacji s bezpieczne wtkowo; pule pocze niemal
zawsze s wykorzystywane przez wiele wtkw, wic ich niezabezpieczanie nie miaoby sensu.

Rozdzia 3. Wspdzielenie obiektw

55

pomagaj osign ten cel zmienne lokalne i klasa ThreadLocal ale nawet z nimi
programista jest w peni odpowiedzialny za to, by odosobnione w wtku obiekty nie
ucieky do innych wtkw.

3.3.1. Odosabnianie w wtku typu ad-hoc


Odosabnianie w wtku typu ad-hoc polega na zastosowaniu tylko i wyczenie implementacyjnych mechanizmw zapewnienia odosobnienia. To podejcie jest dosy
kruche, bo adna z cech jzyka, na przykad widoczno modyfikatorw i zmienne
lokalne, nie pomaga utrzyma obiektu tylko przy jednym wtku. W zasadzie referencje
do obiektw odosobnionych w wtkach, na przykad komponentw wizualnych i modeli
danych w aplikacjach graficznych, s czsto przechowywane w polach publicznych.
Decyzja dotyczca zastosowania odosobnienia wynika najczciej z decyzji o implementacji konkretnego podsystemu, na przykad interfejsu graficznego, jako podsystemu
jednowtkowego. Takie podsystemy niejednokrotnie oferuj znaczce uproszczenie,
ktre okazuje si znacznie bardziej kuszce od kruchoci odosabniania typu ad-hoc10.
Specjalny przypadek odosabniania wtku dotyczy zmiennych ulotnych. Na wspdzielonej zmiennej ulotnej operacje odczyt, modyfikacja, zapis mona przeprowadzi
tylko wtedy, gdy ma si pewno, e zapis bdzie odbywa si wycznie w jednym
wtku. W tej sytuacji zapewniamy odosobnienie modyfikacji w jednym wtku, by
unikn wycigu. Wtedy ulotno gwarantuje, e pozostae wtki zawsze bd widziay
najbardziej aktualn warto zmiennej.
Z powodu swej kruchoci warto oszczdnie stosowa odosabnianie typu ad-hoc i gdy
tylko to moliwe, uywa silniejszych form ograniczania (wykorzystujc stos lub
obiekty ThreadLocal).

3.3.2. Odosobnienie na stosie


Odosobnienie na stosie to szczeglny przypadek odosobnienia w wtku, gdy obiekt
jest dostpny tylko przy uyciu zmiennych lokalnych. Podobnie jak hermetyzacja uatwia
utrzymanie niezmiennikw, tak zmienne lokalne uatwiaj zatrzymanie obiektw
w jednym wtku. Zmienne lokalne s same z siebie ograniczone do wtku wykonujcego
zadanie, bo istniej na jego stosie, ktry nie jest dostpny w innych wtkach. Ten sposb
odosabniania, nazywany rwnie wewntrzwtkowym, jest prostszy od utrzymania
i mniej kruchy ni rozwizanie ad-hoc.
Zmienne lokalne typw prostych, na przykad numPairs z metody loadAndArk() z listingu
3.9, uniemoliwiaj zamanie odosobnienia na stosie, nawet gdyby chciaoby si je
wykona. Nie mona uzyska referencji do zmiennej typu prostego, wic sama semantyka
jzyka ogranicza zmienne lokalne typw prostych do jednego wtku.
10

Kolejnym powodem stosowania podsystemu jednowtkowego bywa ch uniknicia blokad wzajemnych;


to jeden z gwnych powodw, dla ktrych wikszo systemw graficznych jest jednowtkowa.
Systemy jednowtkowe opisuje rozdzia 9.

Cz I Podstawy

56
Listing 3.9. Odosobnienie zmiennych lokalnych typw prostych i referencyjnych
public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null;

// animals ograniczony do metody, wic nie pozwl mu uciec!


animals = new TreeSet<Animal>(new SpeciesGenderComparator());
animals.addAll(candidates);
for (Animal a : animals) {
if (candidate == null || !candidate.isPotentialMate(a))
candidate = a;
else {
ark.load(new AnimalPair(candidate a));
++numPairs;
candidate = null;
}
}
return numPairs;

Odosobnienie na stosie referencji do obiektw wymaga ju nieco uwagi ze strony


programisty, by referencja nie ucieka. W metodzie loadAndArk() tworzymy obiekt
TreeSet i zapamitujemy w nim referencj do animals. W tym momencie istnieje tylko
jedna referencja do zbioru przechowywana w zmiennej lokalnej, wic jest ograniczona
jedynie do wtku wykonujcego metod. Jeli jednak chcemy opublikowa referencj
do zbioru (lub ktregokolwiek z jej elementw), odosobnienie przestanie istnie i referencja animals moe uciec.
Uycie obiektu niezabezpieczonego wtkowo wewntrz kontekstu tylko jednego wtku
jest w peni bezpieczne. Uwaaj: wymagania projektowe, by obiekt by ograniczony
tylko do wtku wykonujcego lub wiedza o tym, e obiekt ten nie jest bezpieczny
wtkowo, istnieje najczciej tylko w gowach programistw piszcych kod. Jeli zaoenie ograniczenia uycia obiektu tylko do jednego wtku nie zostao jawnie okrelone
w dokumentacji, kolejni programici mog dopuci do ucieczki referencji do obiektu.

3.3.3. Obiekt ThreadLocal


Bardziej formalnym sposobem uzyskania odosobnienia w wtku jest obiekt ThreadLocal,
ktry umoliwia przechowywanie wartoci powizanej z konkretnym wtkiem. Obiekt
zawiera metody dostpowe uatwiajce przechowywanie osobnej kopii wartoci dla
kadego wtku, ktry korzysta z kodu. Oznacza to, e metoda get() zawsze zwrci
najnowsz warto ustawion metod set() z poziomu aktualnie wykonywanego wtku.
Zmienne lokalnowtkowe czsto su unikniciu wspdzielenia w projektach bazujcych na zmiennych singletonach i zmiennych globalnych. Przykadowo aplikacja
jednowtkowa moe przechowywa globalny obiekt poczenia bazodanowego inicjalizowany w momencie uruchamiania programu, by unikn przekazywania obiektu
Connection do kadej metody. Poniewa poczenia JDBC mog nie by bezpieczne

Rozdzia 3. Wspdzielenie obiektw

57

wtkowo, aplikacja wielowtkowa uywajca globalnego poczenia bez adnej dodatkowej koordynacji nie jest odpowiednio zabezpieczona. Uywajc obiektu ThreadLocal
do przechowywania poczenia JDBC (patrz listing 3.10), kady wtek uzyskuje wasne
poczenie bazodanowe.
Listing 3.10. Uycie ThreadLocal do zapewnienia odosobnienia w wtku
private Thread ocal<Connection> connectionHolder
= new Thread ocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_UR );
};
};
public Connection getConnection() {
return connectionHolder.get();
}

Technik t mona zastosowa rwnie wtedy, gdy czsto wykonywana operacja wymaga obiektu tymczasowego, na przykad bufora, i chce si unikn kadorazowej
alokacji pamici dla niego. Na przykad przed Jav 5.0 metoda Integer.toString()
uywaa obiektu ThreadLocal do przechowywania 12-bajtowego bufora uywanego
do formatowania wyniku zamiast wspdzielonego statycznego bufora (ktry wymagaby
synchronizacji) i alokacji nowego bufora w kadym wywoaniu11.
Gdy wtek wywouje ThreadLocal.get() po raz pierwszy, initialValue zostaje wypeniona wartoci domyln dla tego wtku. W zasadzie mona traktowa ThreadLocal<T>
jak odwzorowanie Map<Thread,T>, ktre przechowuje specyficzn warto (w rzeczywistoci implementacja klasy jest inna). Wartoci specyficzne dla wtku znajduj si
w samym obiekcie Thread, wic w momencie jego zakoczenia s usuwane razem
z wtkiem.
Przenoszc aplikacj jednowtkow do rodowiska wielowtkowego, mona zachowa
bezpieczestwo wtkowe, konwertujc wspdzielone zmienne globalne na zmienne
lokalnowtkowe, jeli tylko dopuszcza to sposb uywania zmiennych globalnych.
Bufor stosowany przez ca aplikacj przestaby by szczeglnie uyteczny, gdyby
jego niezalene kopie stosowa kady wtek.
Obiekt ThreadLocal czsto pojawia si w szkieletach aplikacji. Przykadowo kontener
J2EE docza kontekst transakcyjny do wykonywanego wtku na czas trwania wywoania EJB. atwo to zaimplementowa, uywajc statycznego obiektu ThreadLocal
przechowujcego kontekst transakcyjny. Gdy kod szkieletu chce si dowiedzie, jaka
transakcja obecnie obowizuje, sprawdza kontekst transakcyjny zawarty w ThreadLocal.
Podejcie jest wygodne, bo nie wymaga przekazywania kontekstu do kadej metody,
ale czy cay kod, ktry korzysta z tego mechanizmu, ze szkieletem.
11

Technika ta rzadko okazuje si wydajniejsza, chyba e operacj wykonuje si niezwykle czsto,


a alokacja jest kosztowna. W Javie 5.0 zastosowano prostsze podejcie z buforem alokowanym
w kadym wywoaniu, wskazujc na to, e dla tak niewielkiego bufora nie warto stosowa bardziej
zaawansowanego rozwizania, bo nie przynosi adnych korzyci w wydajnoci.

Cz I Podstawy

58

atwo mona naduywa ThreadLocal, traktujc jego waciwoci hermetyzujce jako


licencj na stosowanie zmiennych globalnych lub sposb tworzenia ukrytych argumentw metod. Podobnie jak zmienne globalne, rwnie zmienne lokalnowtkowe mog
utrudnia wielokrotne wykorzystanie i wprowadza ukryte zalenoci midzy klasami.
Z tego wzgldu warto podchodzi do nich ostronie.

3.4. Niezmienno
Kolejny sposb obejcia potrzeby synchronizacji polega na uyciu obiektw niezmiennych [EJ Item 13]. Niemal wszystkie hazardy widocznoci i niepodzielnoci opisywane
do tej pory widoczno niewieych danych, utrata aktualizacji, obserwacja obiektu
w niespjnym stanie dotycz sytuacji, w ktrych wiele wtkw stara si uzyska
w tym samym czasie dostp do zmiennego stanu. Jeeli stan obiektu w ogle si nie
zmienia, wszystkie wskazane ryzyka i komplikacje znikaj same.
Obiekt niezmienny to taki, ktrego stanu nie mona zmieni po jego konstrukcji. Obiekty
takie s bezpieczne pod ktem wtkw; niezmienniki zostaj ustawione w konstruktorze,
a poniewa stan obiektu si nie zmienia, niezmienniki zawsze s poprawne.
Obiekty niezmienne zawsze s bezpieczne pod ktem wtkw.

Obiekty niezmienne s proste. Mog znajdowa si tylko w jednym stanie wyliczonym


w konstruktorze. Jednym z trudniejszych aspektw projektowania programu jest
wskazywanie moliwych stanw zoonych obiektw. Okrelanie stanu obiektu niezmiennego nie sprawia najmniejszych trudnoci.
Obiekty niezmienne s te bezpieczniejsze. Przekazanie zmiennego obiektu do nieznanego kodu (lub jego publikacja w taki sposb, e moe go odnale nieznany kod)
bywa niebezpieczne nieznany kod moe zmodyfikowa stan obiektu lub, co gorsza,
zapamita referencj do obiektu i zmieni go w innym czasie, uywajc innego wtku.
Z drugiej strony, obiektu niezmiennego nie uszkodzi niebezpieczny ani bdny kod,
wic jest bezpieczny i moe by publikowany bez stosowania dodatkowych zabezpiecze [EJ Item 24].
Ani specyfikacja jzyka Java, ani model pamici Javy nie definiuje niezmiennoci
w sposb formalny. Nie jest ona rwnowana prostemu zadeklarowaniu wszystkich
pl obiektu jako final. Obiekt ze wszystkimi polami typu final nadal moe by zmienny, bo nic nie stoi na przeszkodzie, by zawiera referencje do zmiennych obiektw.
Obiekt niezmienny nadal moe uywa wewntrznie obiektw zmiennych do zarzdzania
wasnym stanem, co ilustruje klasa ThreeStooges z listingu 3.11. Cho obiekt Set
przechowujcy imiona jest zmienny, konstrukcja gwnej klasy uniemoliwia modyfikacj zbioru po jego utworzeniu. Referencja stooges jest typu final, wic cay stan
obiektu udostpnia pola tego typu. Ostatni wymg (poprawnego utworzenia) zosta
speniony, bo konstruktor w aden sposb nie moe spowodowa wycieku referencji
this.

Rozdzia 3. Wspdzielenie obiektw

59

Listing 3.11. Klasa niezmienna powstaa na podstawie zmiennego obiektu


@Immutable
public final class ThreeStooges {
private final Set<String> stooges = new HashSet<String>();
public ThreeStooges() {
stooges.add("Moe");
stooges.add(" arry");
stooges.add("Curly");
}

public boolean isStooge(String name) {


return stooges.contains(name);
}

Obiekt jest niezmienny, jeli:


t jego stanu nie mona zmieni po zakoczeniu dziaania konstruktora,
12

t wszystkie jego pola s typu final ,


t jest poprawnie utworzony (referencja this nie ucieknie w trakcie konstrukcji).

Poniewa stan programu zmienia si cay czas, wydawa by si mogo, e zastosowania


obiektw niezmiennych s mocno ograniczone, ale w rzeczywistoci tak nie jest. Istnieje
bardzo powana rnica midzy niezmiennym obiektem, a niezmienn referencj do
obiektu. Stan programu przechowywany w niezmiennych obiektach mona uaktualni,
zastpujc stary obiekt now wersj z nowym stanem. Kolejny podrozdzia zawiera
przykad tej techniki13.

3.4.1. Pola typu final


Sowo kluczowe final, bardziej ograniczona wersja mechanizmu const z C++, obsuguje
konstrukcj obiektw niezmiennych. Pl finalnych (ostatecznych) nie mona modyfikowa (cho obiekty, do ktrych zawieraj referencje, mog si zmienia). Co wicej,
maj one specjalne znaczenie w modelu pamici Javy. To uycie pl finalnych gwarantuje bezpieczestwo inicjalizacyjne (patrz punkt 3.5.2), ktre umoliwia swobodny
dostp i wspdzielenie obiektw niezmiennych.
12

13

Technicznie moliwe jest uzyskanie obiektu niezmiennego bez wszystkich pl ustawionych na final
przykadem jest klasa String ale wykorzystuje to bardzo delikatne uwarunkowania wykluczajce
wycigi i wymaga dobrego zrozumienia modelu pamici Javy. Dla ciekawskich: klasa String leniwie
wylicza skrt tekstu przy pierwszym wywoaniu metody hashCode() i buforuje j w polu niefinalnym.
Wszystko dziaa poprawnie jedynie dlatego, e pole moe uzyska tylko jedn niedomyln warto,
ktra zawsze jest taka sama, bo zostaje wyliczona na podstawie niezmiennego stanu (nie prbuj tego
w domu).
Wielu programistw obawia si, e to podejcie powoduje problemy z wydajnoci. W wielu sytuacjach
s one bezpodstawne. Alokacja jest tasza, ni moe si wydawa, a obiekty niezmienne zwikszaj
wydajno, bo nie potrzebuj blokad czy kopiowania defensywnego. Maj ograniczony wpyw
na szybko mechanizmu odzyskiwania pamici.

Cz I Podstawy

60

Nawet jeli obiekt jest zmienny, okrelenie kilku jego pl jako finalnych uatwia analiz
jego stanu, bo ograniczenie zmiennoci niektrych pl zmniejsza liczb kombinacji
stanu obiektu. Obiekt, ktry jest w wikszoci niezmienny, ale ma jedno lub dwa
zmienne pola, okazuje si atwiejszy do analizy ni obiekt z wieloma zmiennymi polami.
Dodatkowo deklaracja pola jako final informuje innych programistw, e wskazany
element nie bdzie si zmienia.
Podobnie jak zaleca si, by wszystkie pola okrela jako prywatne, jeli nie wymagaj
wikszej widocznoci [EJ Item 12], zaleca si te oznaczanie wszystkich pl niezmieniajcych swego stanu modyfikatorem final.

3.4.2. Przykad uycie volatile


do publikacji obiektw niezmiennych
W klasie UnsafeCachingFactorizer ze strony 36. staralimy si uy dwch obiektw
AtomicReference do zapamitania ostatniej wartoci i rozkadu, ale to podejcie nie
okazywao si bezpieczne wtkowo, bo niemoliwe byo jednoczesne pobranie lub
uaktualnienie obu wartoci. Uycie zmiennych ulotnych rwnie nie rozwizaoby
problemu. Z drugiej strony obiekty niezmienne potrafi czasem zapewni sab form
niepodzielnoci.
Serwlet wyliczajcy rozkad na czynniki wykonuje dwie operacje niepodzielne: aktualizacj bufora i warunek sprawdzajcy, czy bufor zawiera warto, ktr chcemy
wyliczy. Gdy grupa powizanych danych musi dziaa w sposb niepodzielny, warto
rozway utworzenie dla nich klasy stanu niezmiennego, na przykad OneValueCache14
z listingu 3.12.
Listing 3.12. Niezmienny obiekt przechowujcy buforowan warto i jej rozkad
@Immutable
public class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger i
BigInteger[] factors) {
lastNumber = i;
lastFactors = Arrays.copyOf(factors factors.length);
}
public BigInteger[] getFactors(BigInteger i) {
if (lastNumber == null || !lastNumber.equals(i))
return null;

14

Klasa OneValueCopy nie byaby niezmienna, gdyby nie metody pobierajce i wywoania copyOf().
Metoda Arrays.copyOf() pojawia si w Javie 6, ale we wczeniejszych wersjach mona uy
metody clone().

Rozdzia 3. Wspdzielenie obiektw

61

else
return Arrays.copyOf(lastFactors lastFactors.length);

Wycig dotyczcy dostpu lub aktualizacji wielu powizanych zmiennych udaje si


wyeliminowa, uywajc obiektu niezmiennego przechowujcego wszystkie zmienne.
Ze zmiennym obiektem trzeba stosowa blokady, by zapewni niepodzielno; wykorzystujc niezmienny obiekt, wtek uzyskuje do niego dostp, nie martwic si o inny
wtek modyfikujcy jego stan. Jeli konieczne staje si uaktualnienie zmiennych, powstaje nowy obiekt stay, ale inne wtki (widzce starsz wersj) nadal maj dostp
do spjnego stanu.
Klasa VolatileCachedFactorizer z listingu 3.13 uywa klasy OneValueCache do zapamitania wartoci i rozkadu na czynniki. Jeli wtek ustawia ulotne pole cache na referencj
do obiektu OneValueCache, nowe dane od razu widz inne wtki.
Listing 3.13. Buforowanie ostatniego wyniku w referencji ulotnej dotyczcej niezmiennego obiektu
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null null);

public void service(ServletRequest req ServletResponse resp) {


BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i factors);
}
encodeIntoResponse(resp factors);
}

Operacje dotyczce bufora nie interferuj midzy sob, bo klasa OneValueCache jest niezmienna, a pole cache za kadym razem jest udostpniane tylko raz w kadej z istotnych
cieek. To poczenie kilku wartoci powizanych niezmiennikiem w jednym niezmiennym obiekcie oraz uycie referencji typu volatile zapewnia klasie VolatileCachedFactorizer bezpieczestwo wtkowe, cho w ogle nie stosujemy jawnych blokad.

3.5. Bezpieczna publikacja


Do tej pory skupialimy si na zapewnieniu braku publikacji obiektu, czyli zamkniciu
w jednym wtku lub wewntrz innego obiektu. Oczywicie s sytuacje, w ktrych
chcemy wspdzieli obiekty midzy wtkami wtedy musimy zatroszczy si o bezpieczestwo. Niestety, samo umieszczenie referencji do obiektu w polu publicznym,
patrz listing 3.14, nie wystarcza do bezpiecznej publikacji obiektu.

Cz I Podstawy

62
Listing 3.14. Publikacja obiektu bez odpowiedniej synchronizacji. Nie rb tak
// Niezabezpieczona publikacja.
public Holder holder;
public void initialize() {
holder = new Holder(42);
}

Zadziwiajce, jak ten niewinny przykadowy kod potrafi zaszkodzi aplikacji. Z powodu
problemw z widocznoci obiekt Holder moe w innym wtku pojawi si w niespjnym stanie, nawet jeli jego niezmienniki zostay poprawnie ustawione w konstruktorze!
Niepoprawna publikacja umoliwia innemu wtkowi zaobserwowanie czciowo
skonstruowanego obiektu.

3.5.1. Nieodpowiednia publikacja


gdy dobre obiekty id w z stron
Nie warto polega na integralnoci czciowo skonstruowanego obiektu. Wtek obserwujcy spostrzee obiekt w niespjnym stanie, a nastpnie zauway, e jego stan
nagle uleg zmianie, cho tak naprawd nie by on modyfikowany od czasu publikacji.
W rzeczywistoci jeli obiekt Holder z listingu 3.15 zostaby opublikowany w sposb
niezabezpieczony (patrz listing 3.14), wtki inne ni publikujcy po wywoaniu metody
assertSanity() mogyby otrzyma wyjtek AssertionTrror!15
Listing 3.15. Klasa ryzykujca bd, jeli nie zostanie poprawnie opublikowana
public class Holder {
private int n;
public Holder(int n) { this.n = n; }

public void assertSanity() {


if (n != n)
throw new AssertionError("Warunek est prawdziwy.");
}

Poniewa nie uylimy synchronizacji do uwidocznienia obiektu Holder innym wtkom,


mwimy o niepoprawnej publikacji. Niepoprawnie opublikowane obiekty naraamy
na dwie przypadoci. Inne wtki mog zauway niewie warto w polu holder,
a tym samym zobaczy referencj null lub inn warto, cho w rzeczywistoci zostaa
ona ustawiona. Co gorsza, mog uzyska poprawn referencj do obiektu Holder, ale

15

Przedstawiany problem nie dotyczy klasy Holder jako takiej, ale sposobu upublicznienia jej obiektw.
Klas mona zabezpieczy przed niepoprawn publikacj, deklarujc pole n jako final, bo wtedy
obiekty klasy bd niezmienne, patrz punkt 3.5.2.

Rozdzia 3. Wspdzielenie obiektw

63

odczyta z niego niewiey stan16. Aby jeszcze bardziej udziwni sytuacj, wtek moe
za pierwszym razem odczyta nieaktualn warto z wtku, a za drugim razem przeczyta
warto aktualn. Wanie z tego powodu test z metody assertSanity() moe si okaza
prawdziwy i spowodowa zgoszenie wyjtku AssertionTrror.
Cho ryzykujemy powtarzanie si, przypomnijmy, i bardzo dziwne rzeczy mog si
dzia, jeli data jest wspdzielona przez wiele wtkw bez naleytej synchronizacji.

3.5.2. Obiekty niezmienne


i bezpieczestwo inicjalizacji
Poniewa obiekty niezmienne s tak wane, model pamici Javy oferuje specjaln
gwarancj bezpieczestwa inicjalizacji wspdzielonych obiektw niezmiennych. Przekonalimy si, e gdy referencja do obiektu staje si widoczna dla innych wtkw, nie
oznacza to jednoczenie, e to samo dzieje si ze stanem obiektu. Aby zapewni spjny
widok stanu obiektu, potrzebujemy synchronizacji.
Z drugiej strony, obiekty niezmienne mona udostpnia bezpiecznie nawet wtedy,
gdy synchronizacja nie zabezpiecza publikacji referencji do obiektu. Aby zagwarantowa to bezpieczestwo inicjalizacji, naley speni wszystkie wymogi niezmiennoci:
niemodyfikowalny stan, wszystkie pola ustawione na final i odpowiedni konstrukcj
(gdyby klasa Holder z listingu 3.15 bya niezmienna, metoda assertSanity() nie mogaby
zgosi wyjtku nawet w momencie nieodpowiedniej publikacji).
Obiekty niezmienne mog by bezpiecznie stosowane przez dowolne wtki bez dodatkowej synchronizacji, nawet jeli synchronizacja nie suy do jej publikacji.

Ta gwarancja dotyczy wartoci wszystkich pl finalnych poprawnie skonstruowanych


obiektw. Pola finalne s dostpne w sposb bezpieczny bez dodatkowej synchronizacji.
Jeli jednak dotycz one zmiennych obiektw, dostp do stanu tych obiektw nadal
wymaga synchronizacji.

3.5.3. Idiomy bezpiecznej synchronizacji


Obiekty, ktre nie s niezmienne, musz zosta opublikowane w sposb bezpieczny,
co najczciej oznacza synchronizacj zarwno przez wtek tworzcy, jak i konsumujcy.
Na razie skupmy si na zapewnieniu, by wtek konsumujcy zawsze widzia stan
obiektu po publikacji. Dopiero pniej zajmiemy si widocznoci zmian po publikacji.

16

Cho moe si wydawa, e wartoci podane w konstruktorze s pierwszymi zapisywanymi w polach,


w rzeczywistoci jest inaczej. Konstruktor Object przed uruchomieniem waciwego konstruktora
ustawia wszystkie zmienne na wartoci domylne. Kod wtku moe odczyta domyln warto,
ktra tak naprawd jest przestarzaa.

Cz I Podstawy

64

Aby bezpiecznie opublikowa obiekt, zarwno referencja do obiektu, jak i jego stan
musz by widoczne dla innych wtkw dokadnie w tym samym momencie. Poprawnie skonstruowany obiekt bezpiecznie publikuj przez:
t inicjalizacj referencji do obiektu z poziomu elementu static,
t przechowywanie referencji w polu volatile lub obiekcie AtomicReference,
t przechowywanie referencji w polu final poprawnie utworzonego obiektu,
t przechowywanie referencji w polu poprawnie chronionym blokad.

Wewntrzna synchronizacja w kolekcjach zabezpieczonych wtkowo oznacza, e


umieszczanie obiektu w takiej kolekcji (na przykad klasie Vector lub synchronizedList)
spenia ostatni z wymienionych wymogw. Jeli wtek A umieszcza obiekt X w kolekcji
zabezpieczonej wtkowo i wtek B stara si j odczyta, gwarantuje si, e B otrzyma
stan X w takiej wersji, w jakiej zostawi go A, mimo e kod obsugujcy X nie stosuje
adnej jawnej synchronizacji. Biblioteka kolekcji zabezpieczonych wtkowo oferuje
nastpujce gwarancje bezpieczestwa publikacji (cho dokumentacja nie zawsze
wskazuje je dostatecznie mocno):
t Umieszczenie klucza lub wartoci w Hashtable, synchronizedMap
lub ConcurrentMap bezpiecznie publikuje ten element dowolnemu wtkowi,
ktry pobiera go z Map (bezporednio lub za pomoc iteratora).
t Umieszczenie elementu w Vector, CopyOnWriteArrayList,
CopyOnWriteArraySet, synchronizedList lub synchronizedSet bezpiecznie

publikuje go dowolnemu wtkowi, ktry pobiera go z kolekcji.


t Umieszczenie elementu w BlockingQueue lub ConcurrentLinkedQueue

bezpiecznie publikuje go dowolnemu wtkowi, ktry pobiera go z kolejki.


Inne mechanizmy biblioteki klas (na przykad Future i Txchanger) rwnie wprowadzaj
bezpieczestwo publikacji. Zajmiemy si ich zaletami w momencie ich wprowadzania.
Inicjalizacja statyczna to czsto najprostszy i najbezpieczniejszy sposb publikacji
obiektu, ktry moe by wykonany statycznie.
public static Holder holder = new Holder(42);

Inicjalizacja statyczna zachodzi w maszynie wirtualnej w momencie wczytywania klasy.


Z powodu wewntrznej synchronizacji w maszynie wirtualnej mechanizm ten gwarantuje
bezpieczn publikacj obiektu [JLS 12.4.2].

3.5.4. Obiekty technicznie niezmienne


Bezpieczna publikacja wystarcza innym wtkom do bezpiecznego dostpu bez synchronizacji do obiektw, ktre nie bd modyfikowane po publikacji. Mechanizm bezpiecznej publikacji gwarantuje, e opublikowany stan obiektu bdzie widziany przez wszystkie referencje w momencie udostpnienia im referencji do obiektu. Jeli tylko stan ten
nie bdzie ulega zmianom, wystarcza to, by dostp do obiektu by bezpieczny.

Rozdzia 3. Wspdzielenie obiektw

65

Obiekty, ktre w sposb formalny nie s niezmienne, ale ktrych stanu nie mona zmieni po opublikowaniu, nazywamy niezmiennymi technicznie. Nie musz spenia
cisej definicji niezmiennoci z podrozdziau 3.4 wystarczy e s przez program
traktowane tak, jakby rzeczywicie byy niezmienne po publikacji. Wykorzystanie
obiektw niezmiennych technicznie upraszcza implementacj i poprawia wydajno
przez redukcj potrzeby synchronizacji.
Bezpiecznie opublikowane obiekty niezmienne technicznie mog by bezpiecznie
uywane przez dowolny wtek bez dodatkowej synchronizacji.

Przykadowo obiekt Date jest zmienny17, ale gdy bdziemy go stosowa tak, jakby by
niezmienny, nie musimy wprowadza blokad, ktre w przeciwnym razie byyby potrzebne do odpowiedniego wspdzielenia obiektu i synchronizacji. Przypumy, e
chcemy przechowywa obiekt Map zawierajcy daty ostatniego logowania uytkownikw.
public Map<String Date> last ogin =
Collections.synchronizedMap(new HashMap<String Date>());

Jeli wartoci Date nie s modyfikowane po ich umieszczeniu w Map, wtedy synchronizacja zapewniana przez implementacj synchronizedMap wystarcza do poprawnej
publikacji obiektu Date i nie jest potrzebna adna dodatkowa synchronizacja w momencie dostpu do niej.

3.5.5. Obiekty zmienne


Jeli obiekt moe zmienia si po utworzeniu, bezpieczna publikacja zapewnia jedynie
jego widoczno w stanie z momentu publikacji. Synchronizacj trzeba wtedy stosowa
nie tylko w momencie publikacji obiektu, ale rwnie przy kadym dostpie do niego,
by w ten sposb zapewni widoczno kolejnych modyfikacji. Bezpieczne wspdzielenie obiektu zmiennego wymaga bezpiecznej publikacji oraz albo bezpieczestwa
wtkowego, albo ochrony blokad.
Wymagania dotyczce publikacji obiektu zale od jego zmiennoci.
t Obiekty niezmienne mog by publikowane w dowolny sposb.
t Obiekty niezmienne technicznie musz by publikowane bezpiecznie.
t Obiekty zmienne musz by publikowane bezpiecznie i by bezpieczne

wtkowo lub chronione blokad.

3.5.6. Bezpieczne wspdzielenie obiektw


Gdy uzyskujemy referencj do obiektu, powinnimy dokadnie wiedzie, co moemy
z nim zrobi. Czy musimy uzyska blokad przed jego uyciem? Czy moemy zmieni jego stan czy tylko go odczyta? Wiele bdw wspbienoci pojawia si, bo
17

Prawdopodobnie jest to bd projektowy biblioteki klas dotyczcych dat przyp. aut.

66

Cz I Podstawy

programista le zrozumia reguy gry wspdzielonymi obiektami. Publikujc obiekt,


zawsze zaznaczaj, w jaki sposb powinien by obsugiwany.
Oto kilka najwaniejszych strategii korzystania ze wspdzielonych obiektw w aplikacjach wspbienych.
Odosobnienie w wtku. Obiekt odosobniony w wtku naley wycznie do jednego
wtku i jest przez niego modyfikowany.
Wspdzielenie tylko z prawem do odczytu. Obiekt wspdzielony tylko do odczytu
moe by wspbienie odczytywany przez wiele wtkw bez dodatkowej
synchronizacji, ale aden wtek nie powinien go modyfikowa. Obiektami
tego typu s obiekty niezmienne i niezmienne technicznie.
Wspdzielenie z bezpieczestwem wtkowym. Obiekt bezpieczny wtkowo
wewntrznie synchronizuje dostp, wic wiele wtkw moe bezpiecznie korzysta
z jego publicznego interfejsu bez potrzeby dodatkowej synchronizacji.
Ochrona. Obiekt chroniony dostpny jest dopiero po uzyskaniu odpowiedniego
klucza. Obiekty chronione to takie, ktre znajduj si w obiektach bezpiecznych
wtkowo lub zostay opublikowane i s chronione przez konkretn blokad.

You might also like