1. Passaggio di parametri ai thread
Quando avviate un thread, spesso volete passargli un certo «lavoro»: il nome di un file, un intervallo di numeri o un saluto. Un thread senza parametri è come un corriere senza indirizzo: corre per la città ma non sa dove consegnare la pizza.
Come si fa?
In Java i thread si creano di solito in due modi:
- Ereditando dalla classe Thread
- Implementando l'interfaccia Runnable (o usando un'espressione lambda)
Il passaggio dei parametri si fa più spesso tramite il costruttore della classe che implementa Runnable. È semplice, sicuro e chiaro. Rendete i campi final — e proteggerete i parametri da modifiche provenienti da altri thread. L'uso di setter/campi pubblici è foriero di problemi di sincronizzazione.
Esempio 1: thread con parametro tramite costruttore
Miglioriamo l'app di esempio: elaborazione degli ordini degli utenti in un thread separato.
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("Elaboro l'ordine: " + orderId + " nel thread " + Thread.currentThread().getName());
// Qui potrebbe esserci un'elaborazione lunga...
}
}
Creiamo e avviamo i thread:
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();
}
}
Output atteso:
Elaboro l'ordine: ORDER-001 nel thread Thread-0
Elaboro l'ordine: ORDER-002 nel thread Thread-1
Esempio 2: thread con parametro tramite espressione lambda (Java 8+)
Se il compito è semplice, si può fare a meno di una classe separata.
public class Main {
public static void main(String[] args) {
String user1 = "Vasya";
String user2 = "Katya";
Thread thread1 = new Thread(() -> {
System.out.println("Ciao, " + user1 + " da " + Thread.currentThread().getName());
});
Thread thread2 = new Thread(() -> {
System.out.println("Ciao, " + user2 + " da " + Thread.currentThread().getName());
});
thread1.start();
thread2.start();
}
}
Perché non conviene usare setter/getter per passare parametri?
Se passate i parametri tramite setter o campi pubblici, c'è il rischio che un altro thread ne cambi il valore proprio durante l'esecuzione. Questo può portare a bug difficili da individuare. Meglio rendere i campi final e passarli tramite costruttore.
2. Priorità dei thread
In Java ogni thread ha una priorità — un numero intero da 1 a 10, che dice allo scheduler quanto è «importante» quel thread rispetto agli altri. Per impostazione predefinita tutti i thread hanno priorità 5 (Thread.NORM_PRIORITY).
Importante: la priorità è solo un «suggerimento» per il sistema operativo. Non è una garanzia che un thread con priorità 10 lavorerà più velocemente di uno con priorità 1. Tutto dipende dal sistema operativo, dalle sue impostazioni e dal carico attuale.
Come impostare la priorità di un thread?
La classe Thread ha i metodi:
- setPriority(int newPriority)
- getPriority()
E tre costanti standard:
- Thread.MIN_PRIORITY (1)
- Thread.NORM_PRIORITY (5)
- Thread.MAX_PRIORITY (10)
Esempio 3: impostare la priorità di un thread
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Thread " + Thread.currentThread().getName() +
" con priorità " + 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();
}
}
Output atteso (l'ordine delle righe non è garantito!):
Thread LowPriority con priorità 1
Thread HighPriority con priorità 10
Thread NormalPriority con priorità 5
La priorità influisce sull'ordine di esecuzione?
Nella maggior parte dei casi — no. Le priorità possono influire su quanto tempo di CPU viene assegnato a un thread, ma non garantiscono l'ordine di avvio o di completamento. Non basate la logica del programma sulle priorità — usatele come «suggerimenti morbidi», ad esempio per fare in modo che un thread in background non disturbi quello principale.
Tabella: costanti di priorità dei thread
| Costante | Valore | Descrizione |
|---|---|---|
|
1 | Priorità più bassa |
|
5 | Priorità normale |
|
10 | Priorità più alta |
3. Denominazione dei thread
Il nome del thread è un «soprannome» nel mondo del multitasking. Quando avete decine di thread, è più facile fare debug se invece di "Thread-7" nel log vedete "FileUploader-1". È particolarmente importante nell'analisi dei log e nella ricerca degli errori.
Come assegnare un nome a un thread?
Il nome si può indicare direttamente nel costruttore di Thread oppure impostare con il metodo setName. Per conoscere il nome corrente — getName.
Thread t = new Thread(() -> {
System.out.println("Sto lavorando!");
}, "MyThreadName");
t.start();
Esempio 4: thread con nome
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("Sono il thread: " + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("Sono il thread: " + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
Output atteso:
Sono il thread: Downloader
Sono il thread: Uploader
4. Pratica: più thread con parametri, priorità e nomi diversi
Mettiamo tutto insieme in un unico esempio: ogni ordine — un thread separato con il proprio nome e priorità.
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() + "] Ho iniziato l'elaborazione dell'ordine " + orderId +
" (priorità " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // simulazione del lavoro
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] Interrotto!");
}
System.out.println("[" + Thread.currentThread().getName() + "] Ho terminato l'elaborazione dell'ordine " + 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();
}
}
Cosa vedremo:
- Ogni thread stampa quale ordine sta elaborando, il proprio nome e la priorità.
- Il tempo di elaborazione è simulato con Thread.sleep.
- L'ordine di completamento dei thread può non coincidere con la priorità.
5. Dettagli importanti e particolarità
Passaggio dei parametri: solo tramite costruttore!
Così è più sicuro. Dichiarate i parametri come final — non potranno essere modificati dopo la creazione dell'oggetto e nessun altro thread potrà «infilare» i propri valori. È fondamentale per prevenire le race condition sui dati.
Priorità: non costruiteci sopra la logica di business
Le priorità sono come chiedere al cameriere: «Posso avere il caffè un po' più in fretta?». A volte funziona, a volte no. È una raccomandazione per il sistema operativo, non una regola di esecuzione.
Nomi dei thread: usateli per il debug
Fa risparmiare moltissimo tempo nell'analisi dei log. Abituatevi a impostare subito nomi significativi.
6. Errori tipici nel lavoro con parametri e priorità dei thread
Errore n. 1: passare parametri tramite campi pubblici. Se dichiarate i parametri come campi ordinari e li cambiate dopo l'avvio del thread, potete ottenere risultati imprevedibili. Usate campi final e il costruttore.
Errore n. 2: aspettarsi che la priorità garantisca l'ordine. Avete impostato Thread.MAX_PRIORITY — e pensate che il thread sarà sempre il primo? No. Non usate le priorità per la sincronizzazione o per governare la logica.
Errore n. 3: thread senza nome nei log. Quando nei log ci sono solo «Thread-3», «Thread-7» — trovare il colpevole è difficile. Date ai thread nomi significativi usando il costruttore o setName.
Errore n. 4: usare lo stesso oggetto Runnable per più thread. Se l'oggetto Runnable conserva stato e lo passate a più thread, questi «condivideranno» i parametri — strada diretta verso le race condition. Create un Runnable separato per ogni thread.
Errore n. 5: passare parametri tramite metodi set*. Se impostate i parametri tramite metodi set* già dopo la creazione/avvio del thread, i valori possono cambiare «al volo» — classica race condition.
GO TO FULL VERSION