Professional Documents
Culture Documents
Java. Współbieżność Dla Praktyków
Java. Współbieżność Dla Praktyków
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
Wydawnictwo Helion
ul. Kociuszki 1c
44-100 Gliwice
tel. 032 230 98 63
e-mail: helion@helion.pl
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
Cz II
Spis treci
Rozdzia 3.
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);
}
}
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.
47
}
2
Cz I Podstawy
48
49
Rysunek 3.1.
Widoczno
gwarantowana przez
synchronizacj
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();
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.
51
Zmienne ulotne stosuj tylko wtedy, gdy spenione s wszystkie ponisze kryteria:
t zapis zmiennej nie zaley od jej aktualnej wartoci lub gdy mona zapewni
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 " ...
};
}
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.
});
53
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
};
Pule pocze udostpniane przez serwery aplikacji s bezpieczne wtkowo; pule pocze niemal
zawsze s wykorzystywane przez wiele wtkw, wic ich niezabezpieczanie nie miaoby sensu.
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.
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;
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
Cz I Podstawy
58
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.
59
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.
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().
61
else
return Arrays.copyOf(lastFactors lastFactors.length);
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.
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.
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.
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.
16
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.
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.
66
Cz I Podstawy