JavaRush /Blog Java /Random-PL /Synchronizacja wątków. Operator synchroniczny w Javie

Synchronizacja wątków. Operator synchroniczny w Javie

Opublikowano w grupie Random-PL
Cześć! Dzisiaj będziemy nadal rozważać funkcje programowania wielowątkowego i rozmawiać o synchronizacji wątków.
Synchronizacja wątków.  Operator zsynchronizowany - 1
Co to jest „synchronizacja”? Poza dziedziną programowania odnosi się to do pewnego rodzaju konfiguracji, która umożliwia współpracę dwóch urządzeń lub programów. Na przykład smartfon i komputer można zsynchronizować z kontem Google, a konto osobiste w serwisie internetowym można zsynchronizować z kontami w sieciach społecznościowych, aby zalogować się za ich pomocą. Synchronizacja wątków ma podobne znaczenie: konfiguruje sposób, w jaki wątki współdziałają ze sobą. W poprzednich wykładach nasze wątki żyły i działały oddzielnie od siebie. Jeden coś liczył, drugi spał, trzeci wyświetlał coś na konsoli, lecz nie wchodzili ze sobą w interakcję. W prawdziwych programach takie sytuacje są rzadkością. Kilka wątków może aktywnie pracować np. z tym samym zbiorem danych i coś w nim zmieniać. To stwarza problemy. Wyobraź sobie, że wiele wątków zapisuje tekst w tej samej lokalizacji, na przykład w pliku tekstowym lub konsoli. Ten plik lub konsola w tym przypadku staje się zasobem współdzielonym. Wątki nie wiedzą o sobie nawzajem, więc po prostu zapisują wszystko, czym mogą zarządzać w czasie przydzielonym im przez planistę wątków. Na niedawnym wykładzie kursu mieliśmy przykład do czego by to doprowadziło, pamiętajmy o tym: Synchronizacja wątków.  Operator synchroniczny - 2Przyczyna leży w tym, że wątki działały ze współdzielonym zasobem, czyli konsolą, bez koordynowania między sobą działań. Jeśli planista wątków przydzielił czas Wątkowi-1, natychmiast zapisuje wszystko w konsoli. To, co inne wątki zdążyły już napisać, a czego nie udało się napisać, nie jest istotne. Efekt, jak widać, jest fatalny. Dlatego w programowaniu wielowątkowym wprowadzono specjalną koncepcję mutex (od angielskiego „mutex”, „wzajemne wykluczenie” - „wzajemne wykluczenie”) . Celem muteksu jest zapewnienie mechanizmu, dzięki któremu tylko jeden wątek będzie miał dostęp do obiektu w określonym czasie. Jeśli wątek-1 uzyskał muteks obiektu A, inne wątki nie będą miały do ​​niego dostępu, aby cokolwiek w nim zmienić. Dopóki muteks obiektu A nie zostanie zwolniony, pozostałe wątki będą zmuszone czekać. Przykład z życia wzięty: wyobraź sobie, że ty i 10 innych nieznajomych bierzesz udział w szkoleniu. Trzeba na zmianę wyrażać pomysły i dyskutować o czymś. Ale ponieważ widzisz się po raz pierwszy, aby nie przeszkadzać sobie ciągle i nie popaść w zgiełk, stosujesz zasadę „gadającej piłki”: mówić może tylko jedna osoba - ta, która ma piłkę jego ręce. W ten sposób dyskusja okazuje się trafna i owocna. Zatem muteks jest w istocie taką piłką. Jeśli muteks obiektu znajduje się w rękach jednego wątku, inne wątki nie będą mogły uzyskać dostępu do obiektu. Aby utworzyć muteks, nie musisz nic robić: jest on już wbudowany w klasę Object, co oznacza, że ​​ma go każdy obiekt w Javie.

Jak działa operator synchronized w Javie

Zapoznajmy się z nowym słowem kluczowym – synchronizowane . Oznacza określony fragment naszego kodu. Jeśli blok kodu jest oznaczony słowem kluczowym synchronized, oznacza to, że blok może być wykonany tylko przez jeden wątek na raz. Synchronizację można wdrożyć na różne sposoby. Na przykład utwórz całą metodę synchronizowaną:
public synchronized void doSomething() {

   //...logika metody
}
Lub napisz blok kodu, w którym przeprowadzana jest synchronizacja na jakimś obiekcie:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //... pewna logika dostępna dla wszystkich wątków

       synchronized (obj) {

           //logika, która jest dostępna tylko dla jednego wątku naraz
       }
   }
}
Znaczenie jest proste. Jeśli jeden wątek wejdzie do bloku kodu oznaczonego słowem synchronized, natychmiast uzyskuje muteks obiektu, a wszystkie inne wątki, które próbują wejść do tego samego bloku lub metody, zmuszone są czekać, aż poprzedni wątek zakończy pracę i zwolni monitor. Synchronizacja wątków.  Operator synchroniczny - 3Przy okazji! Na kursie wykładów widzieliście już przykłady zsynchronizowanych, ale wyglądały one inaczej:
public void swap()
{
   synchronized (this)
   {
       //...logika metody
   }
}
Temat jest dla Ciebie nowy i oczywiście na początku będzie zamieszanie ze składnią. Dlatego pamiętaj od razu, aby nie pomylić się później w metodach pisania. Te dwie metody pisania oznaczają to samo:
public void swap() {

   synchronized (this)
   {
       //...logika metody
   }
}


public synchronized void swap() {

   }
}
W pierwszym przypadku natychmiast po wejściu do metody tworzysz zsynchronizowany blok kodu. Jest synchronizowany przez obiekt this, czyli bieżący obiekt. W drugim przykładzie umieściłeś słowo synchronized w całej metodzie. Nie ma już potrzeby jawnego wskazywania obiektu, na którym przeprowadzana jest synchronizacja. Gdy cała metoda zostanie oznaczona słowem, metoda ta zostanie automatycznie zsynchronizowana dla wszystkich obiektów klasy. Nie zagłębiajmy się w dyskusję, która metoda jest lepsza. Na razie wybierz to, co lubisz najbardziej :) Najważniejsze jest, aby pamiętać: metodę zsynchronizowaną możesz zadeklarować tylko wtedy, gdy cała logika w niej zawarta jest wykonywana przez jeden wątek w tym samym czasie. Na przykład w tym przypadku doSomething()synchronizacja metody byłaby błędem:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //... pewna logika dostępna dla wszystkich wątków

       synchronized (obj) {

           //logika, która jest dostępna tylko dla jednego wątku naraz
       }
   }
}
Jak widać, fragment metody zawiera logikę, dla której synchronizacja nie jest wymagana. Kod w nim zawarty może być wykonywany przez kilka wątków jednocześnie, a wszystkie krytyczne miejsca są przydzielane do osobnego zsynchronizowanego bloku. I jedna chwila. Spójrzmy pod mikroskopem na nasz przykład z wykładu z wymianą imion:
public void swap()
{
   synchronized (this)
   {
       //...logika metody
   }
}
Uwaga: synchronizacja odbywa się za pomocą this. To znaczy dla konkretnego obiektu MyClass. Wyobraź sobie, że mamy 2 wątki ( Thread-1i Thread-2) i tylko jeden obiekt MyClass myClass. W tym przypadku, jeśli Thread-1metoda zostanie wywołana myClass.swap(), muteks obiektu będzie zajęty, a Thread-2gdy spróbujesz go wywołać, myClass.swap()zawiesi się w oczekiwaniu na zwolnienie muteksu. Jeśli mamy 2 wątki i 2 obiekty MyClass- myClass1i myClass2- na różnych obiektach, nasze wątki mogą z łatwością jednocześnie wykonywać zsynchronizowane metody. Pierwszy wątek robi:
myClass1.swap();
Drugie robi:
myClass2.swap();
W takim przypadku słowo kluczowe synchronized wewnątrz metody swap()nie będzie miało wpływu na działanie programu, ponieważ synchronizacja zostanie przeprowadzona na konkretnym obiekcie. I w tym drugim przypadku mamy 2 obiekty, dlatego wątki nie stwarzają sobie nawzajem problemów. W końcu dwa obiekty mają 2 różne muteksy, a ich przechwycenie nie jest od siebie zależne.

Cechy synchronizacji w metodach statycznych

Ale co, jeśli chcesz zsynchronizować metodę statyczną?
class MyClass {
   private static String name1 = „Olya”;
   private static String name2 = "Lena";

   public static synchronized void swap() {
       String s = name1;
       name1 = name2;
       name2 = s;
   }

}
Nie jest jasne, co w tym przypadku będzie służyć jako mutex. Przecież już zdecydowaliśmy, że każdy obiekt ma muteks. Problem w tym, że do wywołania metody statycznej MyClass.swap()nie potrzebujemy obiektów: metoda jest statyczna! Co dalej? :/ Właściwie nie ma z tym problemu. O wszystko zadbali twórcy Javy :) Jeżeli metoda zawierająca krytyczną logikę „wielowątkową” jest statyczna, synchronizacja zostanie przeprowadzona klasami. Dla większej przejrzystości powyższy kod można przepisać jako:
class MyClass {
   private static String name1 = „Olya”;
   private static String name2 = "Lena";

   public static void swap() {

       synchronized (MyClass.class) {
           String s = name1;
           name1 = name2;
           name2 = s;
       }
   }

}
W zasadzie mogłeś o tym pomyśleć sam: skoro nie ma żadnych obiektów, mechanizm synchronizacji musi być w jakiś sposób „wbudowany” w same klasy. Tak to właśnie jest: możesz także synchronizować między zajęciami.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION