[ Pobierz całość w formacie PDF ]
tuje bezpieczeństwo inicjalizacyjne (patrz punkt 3.5.2), które umożliwia swobodny dostęp i współdzielenie obiektów niezmiennych. 12 Technicznie możliwe jest uzyskanie obiektu niezmiennego bez wszystkich pól ustawionych na final przykładem jest klasa String ale wykorzystuje to bardzo delikatne uwarunkowania wykluczające wyścigi i wymaga dobrego zrozumienia modelu pamięci Javy. Dla ciekawskich: klasa String leniwie wylicza skrót tekstu przy pierwszym wywołaniu metody hashCode() i buforuje ją w polu niefinalnym. Wszystko działa poprawnie jedynie dlatego, że pole może uzyskać tylko jedną niedomyślną wartość, która zawsze jest taka sama, bo zostaje wyliczona na podstawie niezmiennego stanu (nie próbuj tego w domu). 13 Wielu programistów obawia się, że to podejście powoduje problemy z wydajnością. W wielu sytuacjach są one bezpodstawne. Alokacja jest tańsza, niż może się wydawać, a obiekty niezmienne zwiększają wydajność, bo nie potrzebują blokad czy kopiowania defensywnego. Mają ograniczony wpływ na szybkość mechanizmu odzyskiwania pamięci. 60 Część I f& Podstawy Nawet jeśli obiekt jest zmienny, określenie kilku jego pól jako finalnych ułatwia analizę jego stanu, bo ograniczenie zmienności niektórych pól zmniejsza liczbę kombinacji stanu obiektu. Obiekt, który jest w większości 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 programistów, że wskazany element nie będzie się zmieniał. Podobnie jak zaleca się, by wszystkie pola określać jako prywatne, jeśli nie wymagają większej widoczności [EJ Item 12], zaleca się też oznaczanie wszystkich pól nie- zmieniających swego stanu modyfikatorem final. 3.4.2. Przykład użycie volatile do publikacji obiektów niezmiennych W klasie UnsafeCachingFactorizer ze strony 36. staraliśmy się użyć dwóch obiektów AtomicReference do zapamiętania ostatniej wartości i rozkładu, ale to podejście nie okazywało się bezpieczne wątkowo, bo niemożliwe było jednoczesne pobranie lub uaktualnienie obu wartości. Użycie zmiennych ulotnych również nie rozwiązałoby problemu. Z drugiej strony obiekty niezmienne potrafią czasem zapewnić słabą formę niepodzielności. Serwlet wyliczający rozkład na czynniki wykonuje dwie operacje niepodzielne: aktu- alizację bufora i warunek sprawdzający, czy bufor zawiera wartość, którą chcemy wyliczyć. Gdy grupa powiązanych danych musi działać w sposób niepodzielny, warto rozważyć utworzenie dla nich klasy stanu niezmiennego, na przykład OneValueCache14 z listingu 3.12. Listing 3.12. Niezmienny obiekt przechowujący buforowaną wartość i jej rozkład @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 byłaby niezmienna, gdyby nie metody pobierające i wywołania copyOf(). Metoda Arrays.copyOf() pojawia się w Javie 6, ale we wcześniejszych wersjach można użyć metody clone(). Rozdział 3. f& Współdzielenie obiektów 61 else return Arrays.copyOf(lastFactors lastFactors.length); } } Wyścig dotyczący dostępu lub aktualizacji wielu powiązanych zmiennych udaje się wyeliminować, używając obiektu niezmiennego przechowującego wszystkie zmienne. Ze zmiennym obiektem trzeba stosować blokady, by zapewnić niepodzielność; wykorzy- stując niezmienny obiekt, wątek uzyskuje do niego dostęp, nie martwiąc się o inny wątek modyfikujący jego stan. Jeśli konieczne staje się uaktualnienie zmiennych, po- wstaje nowy obiekt stały, ale inne wątki (widzące starszą wersję) nadal mają dostęp do spójnego stanu. Klasa VolatileCachedFactorizer z listingu 3.13 używa klasy OneValueCache do zapamię- tania wartości i rozkładu na czynniki. Jeśli wątek ustawia ulotne pole cache na referencję do obiektu OneValueCache, nowe dane od razu widzą inne wątki. Listing 3.13. Buforowanie ostatniego wyniku w referencji ulotnej dotyczącej 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 dotyczące bufora nie interferują między sobą, bo klasa OneValueCache jest nie- zmienna, a pole cache za każdym razem jest udostępniane tylko raz w każdej z istotnych ścieżek. To połączenie kilku wartości powiązanych niezmiennikiem w jednym nie- zmiennym obiekcie oraz użycie referencji typu volatile zapewnia klasie VolatileCa- chedFactorizer bezpieczeństwo wątkowe, choć w ogóle nie stosujemy jawnych blokad. 3.5. Bezpieczna publikacja
[ Pobierz całość w formacie PDF ] zanotowane.pldoc.pisz.plpdf.pisz.plgrolux.keep.pl
|