Java - wielowątkowość
Java to wielowątkowy język programowania, co oznacza, że możemy tworzyć wielowątkowe programy przy użyciu języka Java. Program wielowątkowy zawiera dwie lub więcej części, które mogą działać jednocześnie, a każda część może jednocześnie obsługiwać inne zadanie, optymalnie wykorzystując dostępne zasoby, szczególnie gdy komputer ma wiele procesorów.
Z definicji wielozadaniowość to sytuacja, w której wiele procesów korzysta ze wspólnych zasobów przetwarzania, takich jak procesor. Wielowątkowość rozszerza ideę wielozadaniowości na aplikacje, w których można podzielić określone operacje w ramach jednej aplikacji na osobne wątki. Każdy z wątków może działać równolegle. System operacyjny dzieli czas przetwarzania nie tylko na różne aplikacje, ale także na każdy wątek w aplikacji.
Wielowątkowość umożliwia pisanie w taki sposób, że wiele działań może być wykonywanych jednocześnie w tym samym programie.
Cykl życia wątku
Wątek przechodzi przez różne etapy swojego cyklu życia. Na przykład wątek rodzi się, uruchamia, działa, a następnie umiera. Poniższy diagram przedstawia pełny cykl życia wątku.
Oto etapy cyklu życia -
New- Nowy wątek rozpoczyna swój cykl życia w nowym stanie. Pozostaje w tym stanie, dopóki program nie uruchomi wątku. Jest również określany jako plikborn thread.
Runnable- Po uruchomieniu nowo powstałego wątku wątek staje się gotowy do uruchomienia. Uważa się, że wątek w tym stanie wykonuje swoje zadanie.
Waiting- Czasami wątek przechodzi w stan oczekiwania, podczas gdy wątek oczekuje na wykonanie zadania przez inny wątek. Wątek przechodzi z powrotem do stanu, który można uruchomić, tylko wtedy, gdy inny wątek sygnalizuje oczekującemu wątkowi, aby kontynuował wykonywanie.
Timed Waiting- Działający wątek może wejść w czasowy stan oczekiwania przez określony przedział czasu. Wątek w tym stanie przechodzi z powrotem do stanu, który można uruchomić, po wygaśnięciu tego przedziału czasu lub wystąpieniu zdarzenia, na które oczekuje.
Terminated (Dead) - Wątek, który można uruchomić, przechodzi w stan przerwania po zakończeniu zadania lub w inny sposób.
Priorytety wątków
Każdy wątek Java ma priorytet, który pomaga systemowi operacyjnemu określić kolejność planowania wątków.
Priorytety wątków Java znajdują się w zakresie od MIN_PRIORITY (stała 1) do MAX_PRIORITY (stała 10). Domyślnie każdy wątek ma priorytet NORM_PRIORITY (stała 5).
Wątki o wyższym priorytecie są ważniejsze dla programu i powinny mieć przydzielony czas procesora przed wątkami o niższym priorytecie. Jednak priorytety wątków nie mogą zagwarantować kolejności wykonywania wątków i są bardzo zależne od platformy.
Utwórz wątek, implementując uruchamialny interfejs
Jeśli twoja klasa ma być wykonywana jako wątek, możesz to osiągnąć, implementując plik Runnableberło. Będziesz musiał wykonać trzy podstawowe kroki -
Krok 1
Pierwszym krokiem jest zaimplementowanie metody run () udostępnianej przez plik Runnableberło. Ta metoda zapewnia punkt wejścia dla wątku, a całą logikę biznesową umieścisz w tej metodzie. Poniżej znajduje się prosta składnia metody run () -
public void run( )
Krok 2
W drugim kroku utworzysz instancję Thread obiekt przy użyciu następującego konstruktora -
Thread(Runnable threadObj, String threadName);
Gdzie threadObj jest wystąpieniem klasy, która implementujeRunnable interfejs i threadName to nazwa nadana nowemu wątkowi.
Krok 3
Po utworzeniu obiektu Thread możesz go uruchomić, wywołując start()metoda, która wykonuje wywołanie metody run (). Poniżej znajduje się prosta składnia metody start () -
void start();
Przykład
Oto przykład, który tworzy nowy wątek i uruchamia go -
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
To da następujący wynik -
Wynik
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Utwórz wątek, rozszerzając klasę wątku
Drugim sposobem na utworzenie wątku jest utworzenie nowej klasy, która rozszerza Threadw dwóch prostych krokach. Takie podejście zapewnia większą elastyczność w obsłudze wielu wątków utworzonych przy użyciu metod dostępnych w klasie Thread.
Krok 1
Będziesz musiał zmienić run( )metoda dostępna w klasie Thread. Ta metoda zapewnia punkt wejścia dla wątku, a całą logikę biznesową umieścisz w tej metodzie. Poniżej znajduje się prosta składnia metody run () -
public void run( )
Krok 2
Po utworzeniu obiektu Thread możesz go uruchomić, wywołując start()metoda, która wykonuje wywołanie metody run (). Poniżej znajduje się prosta składnia metody start () -
void start( );
Przykład
Oto poprzedni program przepisany w celu rozszerzenia wątku -
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
To da następujący wynik -
Wynik
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Metody wątku
Poniżej znajduje się lista ważnych metod dostępnych w klasie Thread.
Sr.No. | Metoda i opis |
---|---|
1 | public void start() Uruchamia wątek w oddzielnej ścieżce wykonywania, a następnie wywołuje metodę run () w tym obiekcie Thread. |
2 | public void run() Jeśli wystąpienie tego obiektu Thread zostało utworzone przy użyciu oddzielnego elementu docelowego Runnable, metoda run () jest wywoływana na tym obiekcie Runnable. |
3 | public final void setName(String name) Zmienia nazwę obiektu Thread. Istnieje również metoda getName () do pobierania nazwy. |
4 | public final void setPriority(int priority) Ustawia priorytet tego obiektu Thread. Możliwe wartości mieszczą się w przedziale od 1 do 10. |
5 | public final void setDaemon(boolean on) Parametr true oznacza ten wątek jako wątek demona. |
6 | public final void join(long millisec) Bieżący wątek wywołuje tę metodę w drugim wątku, powodując blokowanie bieżącego wątku do momentu zakończenia drugiego wątku lub przejścia określonej liczby milisekund. |
7 | public void interrupt() Przerywa ten wątek, powodując kontynuację wykonywania, jeśli został zablokowany z jakiegokolwiek powodu. |
8 | public final boolean isAlive() Zwraca wartość true, jeśli wątek żyje, czyli w dowolnym momencie po uruchomieniu wątku, ale przed zakończeniem. |
Poprzednie metody są wywoływane na określonym obiekcie Thread. Następujące metody w klasie Thread są statyczne. Wywołanie jednej z metod statycznych wykonuje operację na aktualnie uruchomionym wątku.
Sr.No. | Metoda i opis |
---|---|
1 | public static void yield() Powoduje, że aktualnie działający wątek podlega innym wątkom o tym samym priorytecie, które czekają na zaplanowanie. |
2 | public static void sleep(long millisec) Powoduje, że aktualnie działający wątek blokuje się na co najmniej określoną liczbę milisekund. |
3 | public static boolean holdsLock(Object x) Zwraca wartość true, jeśli bieżący wątek utrzymuje blokadę na danym Object. |
4 | public static Thread currentThread() Zwraca odwołanie do aktualnie uruchomionego wątku, który jest wątkiem, który wywołuje tę metodę. |
5 | public static void dumpStack() Wyświetla ślad stosu dla aktualnie uruchomionego wątku, co jest przydatne podczas debugowania aplikacji wielowątkowej. |
Przykład
Poniższy program ThreadClassDemo przedstawia niektóre z tych metod klasy Thread. Rozważ klasęDisplayMessage które implementuje Runnable -
// File Name : DisplayMessage.java
// Create a thread to implement Runnable
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) {
this.message = message;
}
public void run() {
while(true) {
System.out.println(message);
}
}
}
Poniżej znajduje się kolejna klasa, która rozszerza klasę Thread -
// File Name : GuessANumber.java
// Create a thread to extentd Thread
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) {
this.number = number;
}
public void run() {
int counter = 0;
int guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " guesses " + guess);
counter++;
} while(guess != number);
System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");
}
}
Poniżej znajduje się główny program, który korzysta z wyżej zdefiniowanych klas -
// File Name : ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String [] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
System.out.println("Starting hello thread...");
thread1.start();
Runnable bye = new DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starting goodbye thread...");
thread2.start();
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try {
thread3.join();
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
}
}
Spowoduje to następujący wynik. Możesz próbować tego przykładu wielokrotnie, a za każdym razem otrzymasz inny wynik.
Wynik
Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......
Główne koncepcje wielowątkowości w języku Java
Robiąc programowanie wielowątkowe w Javie, musisz mieć bardzo przydatne następujące pojęcia -
Co to jest synchronizacja wątków?
Obsługa komunikacji między wątkami
Obsługa zakleszczenia wątków
Główne operacje wątkowe