CodeGym /Blog Java /Random-PL /Razem lepiej: Java i klasa Thread. Część II — Synchroniza...
John Squirrels
Poziom 41
San Francisco

Razem lepiej: Java i klasa Thread. Część II — Synchronizacja

Opublikowano w grupie Random-PL

Wstęp

Wiemy więc, że Java ma wątki. Można o tym przeczytać w recenzji zatytułowanej Better together: Java and the Thread class. Część I — Wątki egzekucyjne . Wątki są niezbędne do wykonywania pracy równolegle. To sprawia, że ​​jest wysoce prawdopodobne, że wątki będą w jakiś sposób oddziaływać na siebie. Przyjrzyjmy się, jak to się dzieje i jakimi podstawowymi narzędziami dysponujemy. Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 1

Dawać

Thread.yield() jest kłopotliwy i rzadko używany. W Internecie jest to opisywane na wiele różnych sposobów. W tym niektórzy ludzie piszą, że jest jakaś kolejka wątków, w której wątek będzie opadał na podstawie priorytetów wątków. Inni piszą, że wątek zmieni swój status z „Running” na „Runnable” (chociaż nie ma rozróżnienia między tymi statusami, tj. Java ich nie rozróżnia). Rzeczywistość jest taka, że ​​to wszystko jest znacznie mniej znane, a jednak w pewnym sensie prostsze. Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 2Wystąpił błąd ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) zarejestrowany w yield()dokumentacji metody. Jeśli to przeczytasz, stanie się jasne, żeyield()w rzeczywistości zawiera tylko pewne zalecenia dla programu planującego wątki Java, że ​​ten wątek może mieć krótszy czas wykonania. Ale to, co faktycznie się dzieje, tj. czy program planujący działa zgodnie z zaleceniami i co ogólnie robi, zależy od implementacji JVM i systemu operacyjnego. A może to też zależeć od innych czynników. Całe zamieszanie wynika najprawdopodobniej z faktu, że wielowątkowość została ponownie przemyślana wraz z rozwojem języka Java. Przeczytaj więcej w przeglądzie tutaj: Krótkie wprowadzenie do Java Thread.yield() .

Spać

Wątek może przejść w stan uśpienia podczas wykonywania. Jest to najłatwiejszy rodzaj interakcji z innymi wątkami. System operacyjny, na którym działa wirtualna maszyna Java, na której działa nasz kod Java, ma własny harmonogram wątków . Decyduje, który wątek rozpocząć i kiedy. Programista nie może wchodzić w interakcje z tym harmonogramem bezpośrednio z kodu Java, tylko za pośrednictwem maszyny JVM. Może poprosić program planujący o wstrzymanie wątku na chwilę, czyli uśpienie go. Możesz przeczytać więcej w tych artykułach: Thread.sleep() i Jak działa wielowątkowość . Możesz także sprawdzić, jak działają wątki w systemach operacyjnych Windows: Internals of Windows Thread . A teraz zobaczmy to na własne oczy. Zapisz następujący kod w pliku o nazwie HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Jak widać mamy jakieś zadanie, które czeka 60 sekund, po czym program się kończy. Kompilujemy za pomocą polecenia „ javac HelloWorldApp.java”, a następnie uruchamiamy program za pomocą „ java HelloWorldApp”. Program najlepiej uruchomić w osobnym oknie. Na przykład w systemie Windows wygląda to tak: start java HelloWorldApp. Używamy polecenia jps, aby uzyskać PID (identyfikator procesu) i otwieramy listę wątków za pomocą „ jvisualvm --openpid pid: Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 3Jak widać, nasz wątek ma teraz status „Uśpiony”. W rzeczywistości istnieje bardziej elegancki sposób pomocy nasz wątek ma słodkie sny:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Czy zauważyliście, że zajmujemy się InterruptedExceptionwszędzie? Zrozummy dlaczego.

przerwanie wątku()

Chodzi o to, że gdy wątek czeka/uśpi, ktoś może chcieć przerwać. W tym przypadku zajmujemy się InterruptedException. Mechanizm ten powstał po Thread.stop()zadeklarowaniu metody jako Deprecated, czyli przestarzałej i niepożądanej. Powodem było to, że po stop()wywołaniu metody wątek został po prostu „zabity”, co było bardzo nieprzewidywalne. Nie mogliśmy wiedzieć, kiedy wątek zostanie zatrzymany, i nie mogliśmy zagwarantować spójności danych. Wyobraź sobie, że zapisujesz dane do pliku, podczas gdy wątek jest zabijany. Zamiast zabijać wątek, twórcy Javy zdecydowali, że bardziej logicznie będzie powiedzieć mu, że powinien zostać przerwany. To, jak odpowiedzieć na te informacje, zależy od samego wątku. Aby uzyskać więcej informacji, przeczytaj Dlaczego Thread.stop jest przestarzały?na stronie internetowej Oracle. Spójrzmy na przykład:

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
W tym przykładzie nie będziemy czekać 60 sekund. Zamiast tego natychmiast wyświetlimy komunikat „Przerwano”. Dzieje się tak, ponieważ wywołaliśmy interrupt()metodę w wątku. Ta metoda ustawia wewnętrzną flagę o nazwie „stan przerwania”. Oznacza to, że każdy wątek ma wewnętrzną flagę, która nie jest bezpośrednio dostępna. Ale mamy natywne metody interakcji z tą flagą. Ale to nie jedyny sposób. Wątek może działać, nie czekać na coś, po prostu wykonywać akcje. Ale może przewidywać, że inni będą chcieli zakończyć jego pracę w określonym czasie. Na przykład:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
W powyższym przykładzie whilepętla będzie wykonywana, dopóki wątek nie zostanie przerwany z zewnątrz. Jeśli chodzi o isInterruptedflagę, ważne jest, aby wiedzieć, że jeśli złapiemy InterruptedException, flaga isInterrupted zostanie zresetowana, a następnie isInterrupted()zwróci fałsz. Klasa Thread ma również statyczną metodę Thread.interrupted() , która ma zastosowanie tylko do bieżącego wątku, ale ta metoda resetuje flagę do wartości false! Przeczytaj więcej w tym rozdziale zatytułowanym Przerwanie wątku .

Dołącz (poczekaj na zakończenie innego wątku)

Najprostszym rodzajem oczekiwania jest oczekiwanie na zakończenie innego wątku.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
W tym przykładzie nowy wątek będzie uśpiony przez 5 sekund. W tym samym czasie główny wątek będzie czekał, aż uśpiony wątek się obudzi i zakończy swoją pracę. Jeśli spojrzysz na stan wątku w JVisualVM, to będzie on wyglądał tak: Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 4Dzięki narzędziom monitorującym możesz zobaczyć, co dzieje się z wątkiem. Metoda joinjest dość prosta, ponieważ jest to po prostu metoda z kodem Java, która jest wykonywana wait()tak długo, jak długo żyje wątek, w którym została wywołana. Gdy tylko wątek umrze (kiedy zakończy swoją pracę), oczekiwanie zostaje przerwane. I to jest cała magia metody join(). Przejdźmy więc do najciekawszej rzeczy.

Monitor

Wielowątkowość obejmuje koncepcję monitora. Słowo monitor pochodzi z języka angielskiego z XVI-wiecznej łaciny i oznacza „instrument lub urządzenie używane do obserwacji, sprawdzania lub ciągłego rejestrowania procesu”. W kontekście tego artykułu postaramy się omówić podstawy. Wszystkich, którzy chcą poznać szczegóły, odsyłamy do powiązanych materiałów. Naszą podróż zaczynamy od specyfikacji języka Java (JLS): 17.1. Synchronizacja . Mówi, co następuje: Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 5Okazuje się, że Java używa mechanizmu „monitorowania” do synchronizacji między wątkami. Monitor jest powiązany z każdym obiektem, a wątki mogą go uzyskać lock()lub zwolnić za pomocą unlock(). Następnie na stronie Oracle znajdziemy samouczek: Intrinsic Locks and Synchronization. Ten samouczek mówi, że synchronizacja w Javie jest zbudowana wokół wewnętrznej jednostki zwanej wewnętrzną blokadą lub blokadą monitora . Ta blokada jest często nazywana po prostu „ monitorem ”. Widzimy również ponownie, że każdy obiekt w Javie ma powiązaną wewnętrzną blokadę. Możesz przeczytać Java — wewnętrzne blokady i synchronizacja . Następnie ważne będzie zrozumienie, w jaki sposób obiekt w Javie może być powiązany z monitorem. W Javie każdy obiekt ma nagłówek, w którym przechowywane są wewnętrzne metadane niedostępne dla programisty z kodu, ale których maszyna wirtualna potrzebuje do poprawnej pracy z obiektami. Nagłówek obiektu zawiera „słowo znaku”, które wygląda następująco: Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Oto artykuł JavaWorld, który jest bardzo przydatny: Jak maszyna wirtualna Java przeprowadza synchronizację wątków . Ten artykuł należy połączyć z opisem z sekcji „Podsumowanie” następującego problemu w systemie śledzenia błędów JDK: JDK-8183909 . Możesz przeczytać to samo tutaj: JEP-8183909 . Tak więc w Javie monitor jest powiązany z obiektem i służy do blokowania wątku, gdy wątek próbuje uzyskać (lub uzyskać) blokadę. Oto najprostszy przykład:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Tutaj bieżący wątek (ten, w którym wykonywane są te wiersze kodu) używa słowa synchronizedkluczowego, aby spróbować użyć monitora powiązanego zobject"\zmienna, aby uzyskać/uzyskać blokadę. Jeśli nikt inny nie walczy o monitor (tj. nikt inny nie uruchamia zsynchronizowanego kodu przy użyciu tego samego obiektu), Java może próbować przeprowadzić optymalizację zwaną „blokowaniem stronniczym”. Do słowa znacznika w nagłówku obiektu dodawany jest odpowiedni znacznik oraz zapis o tym, który wątek posiada blokadę monitora. Zmniejsza to narzut wymagany do zablokowania monitora. Jeśli monitor był wcześniej własnością innego wątku, to takie blokowanie nie wystarczy. JVM przełącza się na następny typ blokowania: „blokowanie podstawowe”. Wykorzystuje operacje porównania i zamiany (CAS). Co więcej, samo słowo znaku nagłówka obiektu nie przechowuje już słowa znaku, ale raczej odniesienie do miejsca, w którym jest przechowywane, a znacznik zmienia się, aby JVM zrozumiała, że ​​używamy podstawowego blokowania. Jeśli wiele wątków konkuruje (rywalizuje) o monitor (jeden uzyskał blokadę, a drugi czeka na zwolnienie blokady), wówczas znacznik w słowie znaku zmienia się, a słowo znaku przechowuje teraz odniesienie do monitora jako obiekt — jakiś wewnętrzny byt JVM. Jak stwierdzono w JDK Enchancement Proposal (JEP), ta sytuacja wymaga miejsca w obszarze Native Heap w celu przechowywania tej jednostki. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. a drugi czeka na zwolnienie blokady), a następnie zmienia się znacznik w słowie znakowym, a słowo znacznikowe przechowuje teraz odniesienie do monitora jako obiekt — jakąś wewnętrzną jednostkę JVM. Jak stwierdzono w JDK Enchancement Proposal (JEP), ta sytuacja wymaga miejsca w obszarze Native Heap w celu przechowywania tej jednostki. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. a drugi czeka na zwolnienie blokady), a następnie zmienia się znacznik w słowie znakowym, a słowo znacznikowe przechowuje teraz odniesienie do monitora jako obiekt — jakąś wewnętrzną jednostkę JVM. Jak stwierdzono w JDK Enchancement Proposal (JEP), ta sytuacja wymaga miejsca w obszarze Native Heap w celu przechowywania tej jednostki. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. a słowo znakowe przechowuje teraz odniesienie do monitora jako obiektu — jakiejś wewnętrznej jednostki JVM. Jak stwierdzono w JDK Enchancement Proposal (JEP), ta sytuacja wymaga miejsca w obszarze Native Heap w celu przechowywania tej jednostki. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. a słowo znakowe przechowuje teraz odniesienie do monitora jako obiektu — jakiejś wewnętrznej jednostki JVM. Jak stwierdzono w JDK Enchancement Proposal (JEP), ta sytuacja wymaga miejsca w obszarze Native Heap w celu przechowywania tej jednostki. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. Odniesienie do lokalizacji pamięci tej jednostki wewnętrznej będzie przechowywane w słowie znakowym nagłówka obiektu. Tak więc monitor jest tak naprawdę mechanizmem synchronizacji dostępu do współdzielonych zasobów między wieloma wątkami. JVM przełącza się między kilkoma implementacjami tego mechanizmu. Tak więc, dla uproszczenia, mówiąc o monitorze, tak naprawdę mówimy o zamkach. Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 7

Zsynchronizowane (oczekiwanie na blokadę)

Jak widzieliśmy wcześniej, koncepcja „bloku zsynchronizowanego” (lub „sekcji krytycznej”) jest ściśle związana z koncepcją monitora. Spójrz na przykład:

public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Tutaj główny wątek najpierw przekazuje obiekt zadania do nowego wątku, a następnie natychmiast uzyskuje blokadę i wykonuje na nim długą operację (8 sekund). Przez cały ten czas zadanie nie może być kontynuowane, ponieważ nie może wejść do synchronizedbloku, ponieważ zamek jest już zdobyty. Jeśli wątek nie może uzyskać blokady, będzie czekał na monitor. Gdy tylko dostanie blokadę, będzie kontynuować wykonywanie. Gdy wątek opuszcza monitor, zwalnia blokadę. W JVisualVM wygląda to tak: Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 8Jak widać w JVisualVM, status to „Monitor”, co oznacza, że ​​wątek jest zablokowany i nie może przejąć monitora. Możesz także użyć kodu do określenia statusu wątku, ale nazwy statusów określone w ten sposób nie odpowiadają nazwom używanym w JVisualVM, chociaż są podobne. W tym przypadkuth1.getState()Instrukcja w pętli for zwróci BLOCKED , ponieważ dopóki pętla jest uruchomiona, lockmonitor obiektu jest zajęty przez mainwątek, a th1wątek jest zablokowany i nie może kontynuować, dopóki blokada nie zostanie zwolniona. Oprócz zsynchronizowanych bloków można zsynchronizować całą metodę. Na przykład oto metoda z HashTableklasy:

public synchronized int size() {
	return count;
}
Ta metoda będzie wykonywana tylko przez jeden wątek w danym momencie. Czy naprawdę potrzebujemy zamka? Tak, potrzebujemy tego. W przypadku metod instancji obiekt „ten” (obiekt bieżący) pełni rolę blokady. Interesująca dyskusja na ten temat toczy się tutaj: Czy stosowanie metody zsynchronizowanej zamiast zsynchronizowanego bloku ma zalety? . Jeśli metoda jest statyczna, wówczas blokadą nie będzie obiekt „ten” (ponieważ nie ma obiektu „ten” dla metody statycznej), ale raczej obiekt klasy (na przykład ) Integer.class.

Czekaj (czekając na monitor). metody powiadamiania() i powiadamianiaWszystkie().

Klasa Thread ma inną metodę oczekiwania, która jest powiązana z monitorem. W przeciwieństwie do sleep()and join(), tej metody nie można po prostu wywołać. Jego nazwa to wait(). Metoda waitjest wywoływana na obiekcie powiązanym z monitorem, na który chcemy czekać. Zobaczmy przykład:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
W JVisualVM wygląda to tak: Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 10Aby zrozumieć, jak to działa, pamiętaj, że metody wait()i notify()są powiązane z java.lang.Object. Może wydawać się dziwne, że w Objectklasie znajdują się metody związane z wątkami. Ale przyczyna tego teraz się ujawnia. Jak zapewne pamiętasz, każdy obiekt w Javie ma nagłówek. Nagłówek zawiera różne informacje porządkowe, w tym informacje o monitorze, tj. o stanie zamka. Pamiętaj, że każdy obiekt lub instancja klasy jest powiązana z wewnętrzną jednostką w JVM, zwaną wewnętrzną blokadą lub monitorem. W powyższym przykładzie kod obiektu zadania wskazuje, że wprowadzamy blok synchronizacji dla monitora powiązanego z obiektem lock. Jeśli uda nam się zdobyć blokadę dla tego monitora, towait()jest nazywany. Wątek wykonujący zadanie zwolni lockmonitor obiektu, ale wejdzie do kolejki wątków oczekujących na powiadomienie z lockmonitora obiektu. Ta kolejka wątków nazywa się ZESTAWEM OCZEKIWANIA, co lepiej odzwierciedla jej cel. Oznacza to, że jest to bardziej zestaw niż kolejka. Wątek maintworzy nowy wątek z obiektem zadania, uruchamia go i czeka 3 sekundy. To sprawia, że ​​jest bardzo prawdopodobne, że nowy wątek będzie w stanie uzyskać blokadę przed wątkiem maini dostać się do kolejki monitora. Następnie mainsam wątek wchodzi do lockzsynchronizowanego bloku obiektu i wykonuje powiadomienie wątku za pomocą monitora. Po wysłaniu powiadomienia wątek mainzwalnialockmonitor obiektu, a nowy wątek, który wcześniej czekał na lockzwolnienie monitora obiektu, kontynuuje wykonywanie. Możliwe jest wysłanie powiadomienia tylko do jednego wątku ( notify()) lub jednocześnie do wszystkich wątków w kolejce ( notifyAll()). Przeczytaj więcej tutaj: Różnica między powiadomieniem() a powiadomieniemAll() w Javie . Należy zauważyć, że kolejność powiadomień zależy od sposobu implementacji JVM. Przeczytaj więcej tutaj: Jak rozwiązać problem głodu za pomocą powiadomień i powiadomień? . Synchronizację można przeprowadzić bez określania obiektu. Można to zrobić, gdy synchronizowana jest cała metoda, a nie pojedynczy blok kodu. Na przykład dla metod statycznych blokadą będzie obiekt klasy (uzyskany za pomocą .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Jeśli chodzi o używanie zamków, obie metody są takie same. Jeśli metoda nie jest statyczna, to synchronizacja zostanie przeprowadzona przy użyciu bieżącego instance, czyli przy użyciu this. Nawiasem mówiąc, powiedzieliśmy wcześniej, że możesz użyć getState()metody, aby uzyskać status wątku. Na przykład dla wątku w kolejce oczekującego na monitor statusem będzie WAITING lub TIMED_WAITING, jeśli metoda wait()określiła limit czasu. Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 11

https://stackoverflow.com/questions/36425942/what-is-the-cycle-of-thread-in-java

Cykl życia nici

W trakcie swojego życia status wątku zmienia się. W rzeczywistości zmiany te składają się na cykl życia nici. Gdy tylko wątek zostanie utworzony, jego status to NOWY. W tym stanie nowy wątek nie jest jeszcze uruchomiony, a harmonogram wątków Java jeszcze nic o nim nie wie. Aby program planujący wątki dowiedział się o wątku, musisz wywołać thread.start()metodę. Następnie wątek przejdzie do stanu RUNNABLE. Internet zawiera wiele niepoprawnych diagramów, które rozróżniają stany „Runnable” i „Running”. Ale to błąd, ponieważ Java nie rozróżnia „gotowy do pracy” (uruchamiany) i „działający” (uruchamiany). Kiedy wątek jest żywy, ale nieaktywny (nie jest Runnable), znajduje się w jednym z dwóch stanów:
  • ZABLOKOWANY — oczekiwanie na wejście w sekcję krytyczną, czyli blokadę synchronized.
  • WAITING — oczekiwanie, aż inny wątek spełni jakiś warunek.
Jeśli warunek jest spełniony, program planujący wątki uruchamia wątek. Jeśli wątek oczekuje do określonego czasu, to jego status to TIMED_WAITING. Jeśli wątek już nie działa (jest zakończony lub został zgłoszony wyjątek), to przechodzi w stan ZAKOŃCZONY. Aby dowiedzieć się o stanie wątku, użyj getState()metody. Wątki mają również isAlive()metodę, która zwraca wartość true, jeśli wątek nie jest ZAKOŃCZONY.

BlokadaWsparcie i parkowanie nici

Począwszy od Javy 1.6 pojawił się ciekawy mechanizm o nazwie LockSupport . Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 12Ta klasa kojarzy „zezwolenie” z każdym wątkiem, który go używa. Wywołanie metody park()zwraca natychmiast, jeśli zezwolenie jest dostępne, zużywając zezwolenie w procesie. W przeciwnym razie blokuje. Wywołanie unparkmetody udostępnia zezwolenie, jeśli jeszcze nie jest dostępne. Jest tylko 1 zezwolenie. Dokumentacja Java dla LockSupportodnosi się do Semaphoreklasy. Spójrzmy na prosty przykład:

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Ten kod będzie zawsze czekał, ponieważ teraz semafor ma 0 zezwoleń. A kiedy acquire()w kodzie zostanie wywołana (tj. request the allow), wątek czeka, aż otrzyma zezwolenie. Ponieważ czekamy, musimy sobie poradzić InterruptedException. Co ciekawe, semafor otrzymuje osobny stan wątku. Jeśli spojrzymy w JVisualVM, zobaczymy, że stan to nie „Czekaj”, ale „Park”. Razem lepiej: Java i klasa Thread.  Część II — Synchronizacja — 13Spójrzmy na inny przykład:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
Status wątku będzie OCZEKIWAŁ, ale JVisualVM rozróżnia waitsłowo synchronizedkluczowe i parkklasę LockSupport. Dlaczego jest to LockSupporttakie ważne? Wracamy ponownie do dokumentacji Java i patrzymy na stan wątku WAITING . Jak widać, są tylko trzy sposoby, aby się do niego dostać. Dwa z tych sposobów to wait()i join(). A trzeci jest LockSupport. W Javie zamki można również budować na LockSupporti i oferować narzędzia wyższego poziomu. Spróbujmy użyć jednego. Na przykład spójrz na ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Podobnie jak w poprzednich przykładach, tutaj wszystko jest proste. Obiekt lockczeka, aż ktoś zwolni udostępniony zasób. Jeśli spojrzymy w JVisualVM, zobaczymy, że nowy wątek będzie zaparkowany, dopóki wątek mainnie zwolni blokady. Możesz przeczytać więcej o blokadach tutaj: Java 8 StampedLocks vs. ReadWriteLocks oraz Synchronized and Lock API w Javie. Aby lepiej zrozumieć sposób implementacji blokad, warto przeczytać o Phaserze w tym artykule: Przewodnik po Java Phaser . Mówiąc o różnych synchronizatorach, musisz przeczytać artykuł DZone na temat synchronizatorów Java.

Wniosek

W tym przeglądzie przyjrzeliśmy się głównym sposobom interakcji wątków w Javie. Dodatkowy materiał: Razem lepiej: Java i klasa Thread. Część I — Wątki wykonania Lepiej razem: Java i klasa Thread. Część III — Interakcja Lepiej razem: Java i klasa Thread. Część IV — Callable, Future i friends Razem lepiej: Java i klasa Thread. Część V — Executor, ThreadPool, Fork/Join Better together: Java i klasa Thread. Część VI — Odpalaj!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION