CodeGym/Blog Java/Random-PL/Razem lepiej: Java i klasa Thread. Część I — Wątki egzeku...
John Squirrels
Poziom 41
San Francisco

Razem lepiej: Java i klasa Thread. Część I — Wątki egzekucyjne

Opublikowano w grupie Random-PL

Wstęp

Wielowątkowość była wbudowana w Javę od samego początku. Przyjrzyjmy się pokrótce temu zjawisku, które nazywa się wielowątkowością. Razem lepiej: Java i klasa Thread.  Część I — Wątki egzekucyjne — 1Jako punkt odniesienia przyjmujemy oficjalną lekcję firmy Oracle: „ Lekcja: Aplikacja „Hello World! ”. Nieznacznie zmienimy kod naszego programu Hello World w następujący sposób:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsjest tablicą parametrów wejściowych przekazywanych podczas uruchamiania programu. Zapisz ten kod w pliku o nazwie zgodnej z nazwą klasy i z rozszerzeniem .java. Skompiluj go za pomocą narzędzia javac : javac HelloWorldApp.java. Następnie uruchamiamy nasz kod z pewnym parametrem, na przykład „Roger”: java HelloWorldApp Roger Razem lepiej: Java i klasa Thread.  Część I — Wątki egzekucyjne — 2Nasz kod ma obecnie poważną wadę. Jeśli nie przekażesz żadnego argumentu (tj. wykonasz tylko „java HelloWorldApp”), otrzymamy błąd:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Wystąpił wyjątek (tj. błąd) w wątku o nazwie „main”. Więc Java ma wątki? Tu zaczyna się nasza podróż.

Java i wątki

Aby zrozumieć, czym jest wątek, musisz zrozumieć, jak uruchamia się program Java. Zmieńmy nasz kod w następujący sposób:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			// Do nothing
		}
	}
}
Teraz skompilujmy to ponownie za pomocą javac. Dla wygody uruchomimy nasz kod Java w osobnym oknie. W systemie Windows można to zrobić w następujący sposób: start java HelloWorldApp. Teraz użyjemy narzędzia jps , aby zobaczyć, jakie informacje może nam przekazać Java: Razem lepiej: Java i klasa Thread.  Część I — Wątki egzekucyjne — 3Pierwsza liczba to PID lub identyfikator procesu. Co to jest proces?
A process is a combination of code and data sharing a common virtual address space.
W przypadku procesów różne programy są od siebie odizolowane podczas działania: każda aplikacja korzysta z własnego obszaru pamięci bez ingerencji w inne programy. Aby dowiedzieć się więcej, polecam przeczytanie tego samouczka: Procesy i wątki . Proces nie może istnieć bez wątku, więc jeśli proces istnieje, to ma co najmniej jeden wątek. Ale jak to się dzieje w Javie? Kiedy uruchamiamy program Java, wykonanie rozpoczyna się od mainmetody. To tak, jakbyśmy wchodzili do programu, więc ta specjalna mainmetoda nazywana jest punktem wejścia. Metodą mainmusi być zawsze „public static void”, aby wirtualna maszyna Java (JVM) mogła rozpocząć wykonywanie naszego programu. Aby uzyskać więcej informacji, Dlaczego główna metoda Java jest statyczna?. Okazuje się, że program uruchamiający Java (java.exe lub javaw.exe) jest prostą aplikacją C: ładuje różne biblioteki DLL, które faktycznie składają się na JVM. Program uruchamiający Java wykonuje określony zestaw wywołań Java Native Interface (JNI). JNI to mechanizm łączący świat wirtualnej maszyny Javy ze światem C++. Tak więc program uruchamiający nie jest samą maszyną JVM, ale raczej mechanizmem do jej ładowania. Zna poprawne polecenia do wykonania w celu uruchomienia maszyny JVM. Wie, jak używać wywołań JNI do konfigurowania niezbędnego środowiska. Konfiguracja tego środowiska obejmuje utworzenie głównego wątku, który oczywiście nazywa się „głównym”. Aby lepiej zilustrować, które wątki istnieją w procesie Java, używamy metody jvisualvmnarzędzie, które jest dołączone do JDK. Znając pid procesu, możemy od razu zobaczyć informacje o tym procesie: jvisualvm --openpid <process id> Razem lepiej: Java i klasa Thread.  Część I — Wątki egzekucyjne — 4Co ciekawe, każdy wątek ma swój odrębny obszar w pamięci przydzielony procesowi. Ta struktura pamięci nazywana jest stosem. Stos składa się z ramek. Ramka reprezentuje aktywację metody (niedokończone wywołanie metody). Ramkę można również przedstawić jako element StackTraceElement (zobacz API języka Java dla StackTraceElement ). Więcej informacji na temat pamięci przydzielanej każdemu wątkowi można znaleźć w dyskusji tutaj: „ W jaki sposób Java (JVM) przydziela stos dla każdego wątku ”. Jeśli spojrzysz na Java API i wyszukasz słowo „Thread”, znajdziesz java.lang.Threadklasa. Jest to klasa reprezentująca wątek w Javie i będziemy musieli z nią pracować. Razem lepiej: Java i klasa Thread.  Część I — Wątki egzekucyjne — 5

java.lang.Wątek

W Javie wątek jest reprezentowany przez instancję klasy java.lang.Thread. Powinieneś od razu zrozumieć, że instancje klasy Thread same w sobie nie są wątkami wykonania. To tylko rodzaj API dla wątków niskiego poziomu zarządzanych przez JVM i system operacyjny. Kiedy uruchamiamy JVM za pomocą programu uruchamiającego Java, tworzy ona wątek maino nazwie „główny” i kilka innych wątków porządkowych. Jak stwierdzono w JavaDoc dla klasy Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Istnieją 2 rodzaje wątków: demony i nie-demony. Wątki demona to wątki tła (porządkujące), które wykonują pewne prace w tle. Słowo „demon” odnosi się do demona Maxwella. Możesz dowiedzieć się więcej z tego artykułu w Wikipedii . Jak podano w dokumentacji, JVM kontynuuje wykonywanie programu (procesu) do momentu:
  • Wywoływana jest metoda Runtime.exit ().
  • Wszystkie wątki NON-daemon kończą swoją pracę (bez błędów lub z rzuconymi wyjątkami)
Wynika z tego ważny szczegół: wątki demona można zakończyć w dowolnym momencie. W rezultacie nie ma gwarancji co do integralności ich danych. W związku z tym wątki demonów są odpowiednie do niektórych zadań porządkowych. Na przykład Java ma wątek odpowiedzialny za przetwarzanie finalize()wywołań metod, czyli wątki zaangażowane w Garbage Collector (gc). Każdy wątek jest częścią grupy ( ThreadGroup ). A grupy mogą być częścią innych grup, tworząc pewną hierarchię lub strukturę.
public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Grupy porządkują zarządzanie wątkami. Oprócz grup wątki mają własne procedury obsługi wyjątków. Spójrz na przykład:
public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
Dzielenie przez zero spowoduje błąd, który zostanie przechwycony przez moduł obsługi. Jeśli nie określisz własnego modułu obsługi, JVM wywoła domyślny moduł obsługi, który wyśle ​​ślad stosu wyjątku do StdError. Każdy wątek ma również priorytet. Możesz przeczytać więcej o priorytetach w tym artykule: Java Thread Priority in Multithreading .

Tworzenie wątku

Jak podano w dokumentacji, mamy 2 sposoby na utworzenie wątku. Pierwszym sposobem jest stworzenie własnej podklasy. Na przykład:
public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
Jak widać, praca nad zadaniem odbywa się w run()metodzie, ale sam wątek jest uruchamiany w start()metodzie. Nie należy mylić tych metod: jeśli bezpośrednio wywołamy un()metodę r, to żaden nowy wątek nie zostanie uruchomiony. Jest to start()metoda, która prosi maszynę JVM o utworzenie nowego wątku. Ta opcja, w której dziedziczymy Thread, jest już zła, ponieważ dołączamy Thread do naszej hierarchii klas. Drugą wadą jest to, że zaczynamy naruszać zasadę „pojedynczej odpowiedzialności”. Oznacza to, że nasza klasa jest jednocześnie odpowiedzialna za sterowanie wątkiem i za wykonanie jakiegoś zadania w tym wątku. Jaka jest właściwa droga? Odpowiedź znajduje się w tej samej run()metodzie, którą nadpisujemy:
public void run() {
	if (target != null) {
		target.run();
	}
}
Oto targetsome java.lang.Runnable, które możemy przekazać podczas tworzenia instancji klasy Thread. Oznacza to, że możemy to zrobić:
public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnablebył również funkcjonalnym interfejsem od wersji Java 1.8. Dzięki temu możliwe jest napisanie jeszcze piękniejszego kodu dla zadania wątku:
public static void main(String[] args) {
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Wniosek

Mam nadzieję, że ta dyskusja wyjaśni, czym jest wątek, jak powstają wątki i jakie podstawowe operacje można wykonywać na wątkach. W następnej części postaramy się zrozumieć, w jaki sposób wątki wchodzą ze sobą w interakcję i zbadać cykl życia wątków. Razem lepiej: Java i klasa Thread. Część II — Synchronizacja 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
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy