To rodzaj ankiety na temat typowych problemów z współbieżnością w Javie. Przykładem może być klasyczny impas lub stan wyścigu, a może błędy EDT w Swing. Interesuje mnie zarówno szeroki zakres możliwych problemów, jak i te, które są najczęstsze. Więc zostaw jedną konkretną odpowiedź błędu współbieżności Java na komentarz i głosuj, jeśli zobaczysz napotkany błąd.
java
multithreading
concurrency
Alex Miller
źródło
źródło
Odpowiedzi:
Najczęstszym problemem współbieżności, jaki widziałem, jest niezrozumienie, że pole zapisane przez jeden wątek nie jest gwarantowane przez inny wątek. Typowe zastosowanie tego:
Dopóki przystanku nie jest lotny albo
setStop
irun
nie są zsynchronizowane to nie jest gwarantowane do pracy. Ten błąd jest szczególnie diabelski, ponieważ w 99,999% w praktyce nie będzie to miało znaczenia, ponieważ wątek czytelnika w końcu zobaczy zmianę - ale nie wiemy, jak szybko ją zobaczył.źródło
Mój najbardziej bolesny problem z współbieżnością miał miejsce, gdy dwie różne biblioteki open source zrobiły coś takiego:
Na pierwszy rzut oka wygląda to na dość trywialny przykład synchronizacji. Jednak; ponieważ ciągi są internowane w Javie, dosłowny ciąg
"LOCK"
okazuje się być tym samym wystąpieniemjava.lang.String
(nawet jeśli są one zadeklarowane całkowicie odmiennie od siebie). Wynik jest oczywiście zły.źródło
Jednym z klasycznych problemów jest zmiana obiektu, na którym synchronizujesz podczas synchronizacji:
Inne współbieżne wątki synchronizują się następnie na innym obiekcie, a ten blok nie zapewnia oczekiwanego wzajemnego wykluczenia.
źródło
Częstym problemem jest używanie klas takich jak Calendar i SimpleDateFormat z wielu wątków (często przez buforowanie ich w zmiennej statycznej) bez synchronizacji. Klasy te nie są bezpieczne dla wątków, więc dostęp wielowątkowy ostatecznie spowoduje dziwne problemy z niespójnym stanem.
źródło
Blokowanie z podwójną kontrolą. Ogólnie mówiąc.
Paradygmat, o którym zacząłem uczyć się problemów podczas pracy w BEA, polega na tym, że ludzie sprawdzą singletona w następujący sposób:
To nigdy nie działa, ponieważ inny wątek mógł dostać się do zsynchronizowanego bloku, a s_instance nie jest już pusty. Zatem naturalną zmianą jest to, aby:
To też nie działa, ponieważ model pamięci Java nie obsługuje tego. Musisz zadeklarować s_instance jako niestabilny, aby działał, a nawet wtedy działa tylko na Javie 5.
Ludzie, którzy nie są zaznajomieni z zawiłościami modelu pamięci Java, cały czas to psują .
źródło
Niepoprawna synchronizacja na obiektach zwróconych przez
Collections.synchronizedXXX()
, szczególnie podczas iteracji lub wielu operacji:To źle . Pomimo pojedynczych operacji
synchronized
, stan mapy między wywoływaniemcontains
iput
może być zmieniony przez inny wątek. Powinno być:Lub z
ConcurrentMap
implementacją:źródło
Chociaż prawdopodobnie nie dokładnie to, o co prosisz, najczęstszym problemem związanym z współbieżnością, jaki napotkałem (prawdopodobnie dlatego, że pojawia się w normalnym kodzie jednowątkowym), jest
java.util.ConcurrentModificationException
spowodowane przez takie rzeczy jak:
źródło
Łatwo jest pomyśleć, że zsynchronizowane kolekcje zapewniają większą ochronę niż w rzeczywistości, i zapominają trzymać blokadę między rozmowami. Kilka razy widziałem ten błąd:
Na przykład w drugim wierszu powyżej obie metody
toArray()
isize()
są same w sobie bezpieczne dla wątków, ale wartośćsize()
jest oceniana oddzielnie odtoArray()
, a blokada listy nie jest utrzymywana między tymi dwoma wywołaniami.Jeśli uruchomisz ten kod z innym wątkiem jednocześnie usuwającym elementy z listy, wcześniej czy później skończy się nowy
String[]
zwrócony, który jest większy niż wymagany do przechowywania wszystkich elementów na liście i ma wartości zerowe w ogonie. Łatwo jest myśleć, że ponieważ dwa wywołania metod do Listy występują w jednym wierszu kodu, jest to jakoś operacja atomowa, ale tak nie jest.źródło
Najczęstszym błędem, który widzimy w miejscu, w którym pracuję, jest to, że programiści wykonują długie operacje, takie jak wywołania serwera, na EDT, blokując GUI na kilka sekund i powodując brak reakcji aplikacji.
źródło
Zapominanie o czekaniu () (lub Condition.await ()) w pętli, sprawdzanie, czy warunek oczekiwania jest rzeczywiście spełniony. Bez tego możesz napotkać błędy spowodowane fałszywymi pobudkami wait (). Zastosowanie kanoniczne powinno być:
źródło
Innym częstym błędem jest słaba obsługa wyjątków. Gdy wątek w tle zgłasza wyjątek, jeśli nie poradzisz sobie z nim właściwie, możesz w ogóle nie zobaczyć śladu stosu. A może zadanie w tle przestaje działać i nigdy się nie uruchamia ponownie, ponieważ nie udało się obsłużyć wyjątku.
źródło
Aż wziąłem klasę z Brian Goetz I nie zdawali sobie sprawy, że nie zsynchronizowane
getter
z prywatnym polu zmutowanego poprzez zsynchronizowanesetter
jest nie gwarantuje powrotu zaktualizowaną wartość. Tylko wtedy, gdy zmienna jest chroniona przez zsynchronizowany blok na obu odczytach ORAZ i zapisach , otrzymasz gwarancję najnowszej wartości zmiennej.źródło
Myślisz, że piszesz jednowątkowy kod, ale używasz zmiennych statycznych (w tym singletonów). Oczywiście będą one dzielone między wątkami. Zdarza się to zaskakująco często.
źródło
Arbitralnych wywołań metod nie należy wykonywać zsynchronizowanych bloków.
Dave Ray poruszył tę kwestię w swojej pierwszej odpowiedzi i faktycznie spotkałem się również z impasem związanym z wywoływaniem metod w słuchaczach z poziomu metody zsynchronizowanej. Myślę, że bardziej ogólna lekcja jest taka, że wywołania metod nie powinny być wykonywane „na dziko” z poziomu zsynchronizowanego bloku - nie masz pojęcia, czy wywołanie będzie długotrwałe, spowoduje zakleszczenie, czy cokolwiek innego.
W tym przypadku, i zazwyczaj ogólnie, rozwiązaniem było ograniczenie zakresu synchronizowanego bloku, aby chronić tylko krytyczną prywatną sekcję kodu.
Ponadto, ponieważ teraz uzyskiwaliśmy dostęp do kolekcji słuchaczy poza zsynchronizowanym blokiem, zmieniliśmy ją na kolekcję kopiowania przy zapisie. Albo moglibyśmy po prostu stworzyć obronną kopię Kolekcji. Chodzi o to, że zwykle istnieją alternatywne sposoby bezpiecznego dostępu do zbioru nieznanych obiektów.
źródło
Najnowszym błędem związanym z Współbieżnością był obiekt, który w swoim konstruktorze utworzył usługę ExecutorService, ale kiedy obiekt nie był już przywoływany, nigdy nie zamknął usługi ExecutorService. Tak więc w ciągu tygodni tysiące wycieków wyciekło, ostatecznie powodując awarię systemu. (Technicznie rzecz biorąc, nie zawiesił się, ale przestał działać poprawnie, ale nadal działa.)
Technicznie przypuszczam, że nie jest to problem z współbieżnością, ale jest to problem związany z korzystaniem z bibliotek java.util.concurrency.
źródło
Niezrównoważona synchronizacja, szczególnie w przypadku map, wydaje się dość częstym problemem. Wiele osób uważa, że synchronizacja przy wstawianiu do mapy (nie ConcurrentMap, ale powiedzmy HashMap) i niezsynchronizowanie przy pobieraniu jest wystarczające. Może to jednak prowadzić do nieskończonej pętli podczas ponownego mieszania.
Ten sam problem (częściowa synchronizacja) może wystąpić wszędzie tam, gdzie masz stan współdzielony z odczytami i zapisami.
źródło
Wystąpił problem współbieżności z serwletami, gdy istnieją zmienne pola, które zostaną ustawione przy każdym żądaniu. Ale dla każdego żądania istnieje tylko jedna instancja serwletu, więc działało to doskonale w środowisku jednego użytkownika, ale gdy więcej niż jeden użytkownik zażądał serwletu, wystąpiły nieprzewidziane wyniki.
źródło
Niezupełnie błąd, ale najgorszym grzechem jest udostępnienie biblioteki, z której zamierzają korzystać inni ludzie, ale nie stwierdzenie, które klasy / metody są bezpieczne dla wątków, a które należy wywołać tylko z jednego wątku itp.
Więcej osób powinno korzystać z adnotacji o współbieżności (np. @ThreadSafe, @GuardedBy itp.) Opisanych w książce Goetza.
źródło
Moim największym problemem zawsze były impasy, szczególnie spowodowane przez słuchaczy, którzy zostali zwolnieni z trzymanym zamkiem. W takich przypadkach bardzo łatwo jest uzyskać odwrócone blokowanie między dwoma wątkami. W moim przypadku między symulacją uruchomioną w jednym wątku a wizualizacją symulacji uruchomionej w wątku interfejsu użytkownika.
EDYCJA: Przeniesiono drugą część do oddzielnej odpowiedzi.
źródło
Rozpoczęcie wątku w konstruktorze klasy jest problematyczne. Jeśli klasa zostanie rozszerzona, wątek można uruchomić przed wykonaniem konstruktora podklasy .
źródło
Zmienne klasy we wspólnych strukturach danych
Kiedy tak się dzieje, kod jest znacznie bardziej skomplikowany niż ten uproszczony przykład. Replikacja, znalezienie i naprawienie błędu jest trudne. Być może można by tego uniknąć, gdybyśmy mogli oznaczyć niektóre klasy jako niezmienne, a niektóre struktury danych jako przechowujące tylko niezmienne obiekty.
źródło
Synchronizacja z literałem ciągów lub stałą zdefiniowaną przez literał ciągów jest (potencjalnie) problemem, ponieważ literał ciągu jest internowany i będzie współdzielony przez każdą inną osobę w JVM używającą tego samego literału ciągu. Wiem, że ten problem pojawił się w serwerach aplikacji i innych scenariuszach „kontenerowych”.
Przykład:
W takim przypadku każdy, kto używa ciągu „foo” do zablokowania, dzieli tę samą blokadę.
źródło
Wierzę, że w przyszłości głównym problemem związanym z Javą będą (brak) gwarancji widoczności dla konstruktorów. Na przykład, jeśli utworzysz następującą klasę
a następnie po prostu przeczytaj właściwość MyClass a z innego wątku, MyClass.a może wynosić 0 lub 1, w zależności od implementacji i nastroju JavaVM. Dziś szanse na „a” bycie 1 są bardzo duże. Ale na przyszłych maszynach NUMA może być inaczej. Wiele osób nie zdaje sobie z tego sprawy i uważa, że nie muszą przejmować się wielowątkowością podczas fazy inicjalizacji.
źródło
Najgłupszym błędem, który często popełniam, jest zapominanie o synchronizacji przed wywołaniem powiadomienia () lub oczekiwania () na obiekcie.
źródło
Używanie lokalnego „nowego obiektu ()” jako muteksu.
To jest bezużyteczne.
źródło
Innym częstym problemem „współbieżności” jest używanie zsynchronizowanego kodu, gdy nie jest to wcale konieczne. Na przykład nadal widzę programistów używających
StringBuffer
lub nawetjava.util.Vector
(jako lokalnych zmiennych metody).źródło
Wiele obiektów, które są chronione zamkiem, ale są często dostępne kolejno. Natknęliśmy się na kilka przypadków, w których blokady są uzyskiwane przez inny kod w różnych zamówieniach, co powoduje impas.
źródło
Nie zdając sobie sprawy, że
this
klasa wewnętrzna nie należythis
do klasy zewnętrznej. Zazwyczaj w anonimowej klasie wewnętrznej, która implementujeRunnable
. Główny problem polega na tym, że ponieważ synchronizacja jest częścią wszystkichObject
s, w rzeczywistości nie ma sprawdzania typu statycznego. Widziałem to co najmniej dwa razy na usenecie, i pojawia się również w Brian Goetz'z Java Concurrency in Practice.Zamknięcia BGGA nie cierpią z tego powodu, ponieważ nie ma
this
takiego zamknięcia (this
odnosi się do klasy zewnętrznej). Jeśli użyjesz nie-this
obiektów jako zamków, obejdzie ten problem i inne.źródło
Zastosowanie obiektu globalnego, takiego jak zmienna statyczna do blokowania.
Prowadzi to do bardzo złej wydajności z powodu niezgody.
źródło
Miód? Przed nadejściem
java.util.concurrent
najczęstszym problemem, na który rutynowo natknąłem się, było to, co nazywam „przebijaniem wątków”: aplikacje, które używają wątków do współbieżności, ale spawnują ich zbyt wiele i kończą się przeładowaniem.źródło