Zadanie wyjaśnienie wyniku.

0

Witam
Mam problem z zadaniem:
Przeanalizuj poniższy program. Dwa wątki zwiększają 200 000 razy wspólny licznik
(egzemplarz klasy IntCell) o 1. Po ich zakończeniu wartość licznika powinna oczywiście wynosić 400 000.
Uruchom ten program kilka razy. Jak wyjaśnisz otrzymane wyniki?

Kod: https://4programmers.net/Pastebin/6291

Bardzo proszę o pomoc i wytłumaczenie jeśli to możliwe łopatologicznym językiem ;)

1

W klasie Count jest statyczna instancja Obiektu IntCell. Są tworzone 2 wątki (instancje klasy Count), które naprzemiennie zwiekszają wartość pola n obiektu n typu IntCell o 1. Wynik nie wynosi 400,000 ponieważ, obydwa obiekty/wątki Count odwołują się do tego samego obiektu, który nie jest chroniony w żaden sposób przed dostępem wielowątkowym. Co oznacza, że każda instancja obieku Count może 'widzieć', starą wartość pola n. Na przykład: instacja p zwięszka wartość klasy n o 1 i n wynosi 1. Instancja q zwiększa wartość n o 1 i n wciąż wynosi 1, ponieważ instancja q miała dostęp do przestarzałej wartości pola n. Dzieje się tak, ponieważ w celu zwiększenia wydajności wątki keszują sobie dane lokalnie zamiast korzystać ze wspólnej pamięci. Aby tego uniknąć można użyć mechanizmu synchronizacji i wprowadzić monitor, zapewniając tym samym atomowość operacji i widoczność w pamięci. Można użyć też innych mechanizmów, jak np. CAS

Pewnie to brzmi jak bełkot więc w razie wątpliwości pytaj.

0

Rzuć okiem na ten link
Wykorzystanie słowa kluczowego volatile powinno rozwiązać ten problem.
Tu masz jeszcze po polsku
Oczywiście rozwiązanie @hcubyc też nie będzie złe

1

Oznaczenie pola n klasy IntCell jako volatile nie rozwiąże tego problemu @Burdzi0. Volatile gwarantuje jedynie widoczność w pamięci, nie gwarantuje natomiast atomowości operacji, co z kolei gwarantuje synchronizacja. Przykładowa sytuacja. Wątek p pobiera wartość n i jest zatrzymany przez kernel. W tym samym czasie wątek q pobiera wartość n i zwiększa ją o 1. Następnie zostaje wybudzony wątek p, który dalej kontynuuje swoją pracę, czyli pobrał już wartość i zwiększa jej wartość o 1. W tym przypadku wartość n zamiast być zwiększona o 2 przez dwa różne wątki, zostaje zwiększona o 1 przez dwa różne wątki, ponieważ cała operacja nie jest atomowa.

1

@hcubyc: Nie upieram się, jako że sam jestem jeszcze bardzo niedoświadczony jednakże zaciekawiło mnie to:
Zadeklarowanie zmiennej jako volatile informuje kompilator, że wątki odczytujące wartość takiej zmiennej zawsze powinny widzieć jej aktualną wartość i że powinna być zachowana kolejność operacji na tej zmiennej wynikająca z kodu programu.

Można powiedzieć, że zmienne volatile wprowadzają do programów współbieżnych słabą synchronizację - bez możliwości zawieszenia wątku.

Model pamięci języka Java gwarantuje atomowość operacji odczytu i zapisu zmiennych, ale tylko dla typów o rozmiarze do 32-bitów włącznie. W przypadku typów 64-bitowych, jak double lub long odczyt i zapis zmiennej bywa dzielony na dwie operacje 32-bitowe, co może wpłynąć na wynik programu współbieżnego. Zastosowanie modyfikatora volatile w deklaracji zmiennej gwarantuje, że do tego nie dojdzie.

1

@Burdzi0 sam sobie odpowiedziałeś ;)

Model pamięci języka Java gwarantuje atomowość operacji odczytu i zapisu zmiennych, ale tylko dla typów o rozmiarze do 32-bitów włącznie.

Tak też się dzieje, jednak w przykładzie autora postu działanie składa się z dwóch operacji - odczytu zmiennej, a następnie zmodyfikowanie jej wartości. Każda z tych operacji jest atomowa, ale razem już nie są. Co innego, gdy te dwie operację będą w bloku synchronized - jeżeli będzie on użyty poprawnie to żaden inny wątek nie może przerwać tej operacji jako całości i dlatego jest to 'mocniejszy' mechanizm niż użycie volatile. Volatile nadaje się do aktualizowania zmiennej typu boolean i referencji obiektów niezmiennych, można też używać go do innych celów jak np. prymitywy, ale wtedy trzeba wiedzieć co się robi ;)
W concurrency in practice wszystko jest obszernie wytłumaczone.

1 użytkowników online, w tym zalogowanych: 0, gości: 1