1. Przekazywanie parametrów do wątków
Gdy uruchamiasz wątek, często chcesz przekazać mu jakąś „pracę”: nazwę pliku, zakres liczb albo pozdrowienie. Wątek bez parametrów — to jak kurier bez adresu: biega po mieście, ale nie wie, dokąd dostarczyć pizzę.
Jak to zrobić?
W Javie wątki tworzy się zwykle na dwa sposoby:
- Dziedziczenie po klasie Thread
- Implementacja interfejsu Runnable (lub użycie wyrażenia lambda)
Parametry najczęściej przekazuje się przez konstruktor klasy implementującej Runnable. To proste, bezpieczne i zrozumiałe. Oznacz pola jako final — i ochronisz parametry przed zmianami z innych wątków. Używanie setterów/publicznych pól grozi problemami z synchronizacją.
Przykład 1: Wątek z parametrem przekazywanym przez konstruktor
Rozbudujmy aplikację szkoleniową: przetwarzanie zamówień użytkowników w osobnym wątku.
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("Przetwarzam zamówienie: " + orderId + " w wątku " + Thread.currentThread().getName());
// Tutaj może być długie przetwarzanie...
}
}
Tworzymy i uruchamiamy wątki:
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
thread1.start();
thread2.start();
}
}
Oczekiwany wynik:
Przetwarzam zamówienie: ORDER-001 w wątku Thread-0
Przetwarzam zamówienie: ORDER-002 w wątku Thread-1
Przykład 2: Wątek z parametrem przez wyrażenie lambda (Java 8+)
Jeśli zadanie jest proste, można obejść się bez osobnej klasy.
public class Main {
public static void main(String[] args) {
String user1 = "Alice";
String user2 = "Bob";
Thread thread1 = new Thread(() -> {
System.out.println("Cześć, " + user1 + " z " + Thread.currentThread().getName());
});
Thread thread2 = new Thread(() -> {
System.out.println("Cześć, " + user2 + " z " + Thread.currentThread().getName());
});
thread1.start();
thread2.start();
}
}
Dlaczego nie należy używać setterów/getterów do przekazywania parametrów?
Jeśli przekazujesz parametry przez settery lub publiczne pola, istnieje ryzyko, że inny wątek zmieni wartość w trakcie wykonywania. Może to prowadzić do trudnych do wykrycia błędów. Lepiej oznaczyć pola jako final i przekazywać je przez konstruktor.
2. Priorytety wątków
W Javie każdy wątek ma priorytet — liczbę całkowitą od 1 do 10, która mówi planiscie (schedulerowi), jak „ważny” jest dany wątek w porównaniu z innymi. Domyślnie wszystkie wątki mają priorytet 5 (Thread.NORM_PRIORITY).
Ważne: priorytet to tylko „sugestia” dla systemu operacyjnego. To nie jest gwarancja, że wątek z priorytetem 10 będzie działał szybciej niż wątek z priorytetem 1. Wszystko zależy od systemu, jego ustawień i bieżącego obciążenia.
Jak ustawić priorytet wątku?
Klasa Thread ma metody:
- setPriority(int newPriority)
- getPriority()
I trzy standardowe stałe:
- Thread.MIN_PRIORITY (1)
- Thread.NORM_PRIORITY (5)
- Thread.MAX_PRIORITY (10)
Przykład 3: Ustawianie priorytetu wątku
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Wątek " + Thread.currentThread().getName() +
" z priorytetem " + Thread.currentThread().getPriority());
};
Thread low = new Thread(task, "LowPriority");
Thread norm = new Thread(task, "NormalPriority");
Thread high = new Thread(task, "HighPriority");
low.setPriority(Thread.MIN_PRIORITY); // 1
norm.setPriority(Thread.NORM_PRIORITY); // 5
high.setPriority(Thread.MAX_PRIORITY); // 10
low.start();
norm.start();
high.start();
}
}
Oczekiwany wynik (kolejność linii nie jest gwarantowana!):
Wątek LowPriority z priorytetem 1
Wątek HighPriority z priorytetem 10
Wątek NormalPriority z priorytetem 5
Czy priorytet wpływa na kolejność wykonywania?
W większości przypadków — nie. Priorytety mogą wpływać na to, ile czasu procesor przydziela wątkowi, ale nie gwarantują kolejności startu ani zakończenia. Nie buduj logiki programu na priorytetach — traktuj je jako „miękkie” podpowiedzi, np. aby wątek w tle nie przeszkadzał głównemu.
Tabela: Stałe priorytetów wątków
| Stała | Wartość | Opis |
|---|---|---|
|
1 | Najniższy priorytet |
|
5 | Normalny priorytet |
|
10 | Najwyższy priorytet |
3. Nazywanie wątków
Nazwa wątku to „przydomek” w świecie wielozadaniowości. Gdy masz dziesiątki wątków, łatwiej debugować, jeśli zamiast "Thread-7" w logu zobaczysz "FileUploader-1". To szczególnie ważne podczas analizy logów i szukania błędów.
Jak ustawić nazwę wątku?
Nazwę można podać bezpośrednio w konstruktorze Thread albo ustawić metodą setName. Bieżącą nazwę można pobrać przez getName.
Thread t = new Thread(() -> {
System.out.println("Pracuję!");
}, "MyThreadName");
t.start();
Przykład 4: Nazwane wątki
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("Jestem wątkiem: " + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("Jestem wątkiem: " + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
Oczekiwany wynik:
Jestem wątkiem: Downloader
Jestem wątkiem: Uploader
4. Praktyka: kilka wątków z różnymi parametrami, priorytetami i nazwami
Połączmy wszystko w jednym przykładzie: każde zamówienie — osobny wątek z własną nazwą i priorytetem.
public class OrderProcessor implements Runnable {
private final String orderId;
private final int processingTimeMs;
public OrderProcessor(String orderId, int processingTimeMs) {
this.orderId = orderId;
this.processingTimeMs = processingTimeMs;
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "] Rozpocząłem przetwarzanie zamówienia " + orderId +
" (priorytet " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // imitacja pracy
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] Przerwany!");
}
System.out.println("[" + Thread.currentThread().getName() + "] Zakończyłem przetwarzanie zamówienia " + orderId);
}
public static void main(String[] args) {
Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");
fastOrder.setPriority(Thread.MAX_PRIORITY);
normalOrder.setPriority(Thread.NORM_PRIORITY);
slowOrder.setPriority(Thread.MIN_PRIORITY);
fastOrder.start();
normalOrder.start();
slowOrder.start();
}
}
Co zobaczymy:
- Każdy wątek wypisuje, które zamówienie przetwarza, swoją nazwę i priorytet.
- Czas przetwarzania jest symulowany za pomocą Thread.sleep.
- Kolejność zakończenia wątków może nie pokrywać się z priorytetem.
5. Ważne niuanse i szczegóły
Przekazywanie parametrów: tylko przez konstruktor!
Tak jest bezpieczniej. Oznacz parametry jako final — nie będzie można ich zmienić po utworzeniu obiektu i żaden inny wątek nie „podstawi” swoich wartości. To kluczowe, by zapobiegać wyścigom danych.
Priorytety: nie buduj na nich logiki biznesowej
Priorytety to jak prośba do kelnera: „Czy mogę dostać kawę szybciej?”. Czasem działa, czasem — nie. To wskazówka dla systemu operacyjnego, a nie reguła wykonania.
Nazwy wątków: używaj do debugowania
Daje ogromną oszczędność czasu podczas analizy logów. Wyrób sobie nawyk nadawania od razu sensownych nazw.
6. Typowe błędy przy pracy z parametrami i priorytetami wątków
Błąd nr 1: Przekazywanie parametrów przez pola publiczne. Jeśli deklarujesz parametry jako zwykłe pola i zmieniasz je po starcie wątku, możesz otrzymać nieprzewidywalne rezultaty. Używaj pól final i konstruktora.
Błąd nr 2: Oczekiwanie, że priorytet gwarantuje kolejność. Ustawiłeś Thread.MAX_PRIORITY — i myślisz, że wątek zawsze będzie pierwszy? Nie. Nie używaj priorytetów do synchronizacji ani sterowania logiką.
Błąd nr 3: Nienazwane wątki w logach. Gdy w logach są tylko „Thread-3”, „Thread-7” — trudno znaleźć winowajcę. Nadawaj wątkom sensowne nazwy przy pomocy konstruktora lub setName.
Błąd nr 4: Używanie tego samego obiektu Runnable dla wielu wątków. Jeśli obiekt Runnable przechowuje stan i przekazujesz go wielu wątkom, „dzielą” one parametry — prosta droga do wyścigów danych. Twórz osobny Runnable na wątek.
Błąd nr 5: Przekazywanie parametrów przez metody set*. Jeśli ustawiasz parametry przez set* już po utworzeniu/uruchomieniu wątku, wartości mogą zmienić się „w locie” — klasyczny race condition.
GO TO FULL VERSION