CodeGym /Corsi /JAVA 25 SELF /Avvio dei thread: Thread e Runnable, sintassi

Avvio dei thread: Thread e Runnable, sintassi

JAVA 25 SELF
Livello 51 , Lezione 1
Disponibile

1. Classe Thread: il primo thread in Java

In Java ogni thread di esecuzione è rappresentato da un oggetto della classe Thread. Questa classe — come un direttore d'orchestra: gestisce l'avvio, l'arresto e il ciclo di vita del thread.

Per avviare un thread, occorre:

  1. Creare un oggetto della classe Thread (o di una sua sottoclasse).
  2. Invocare su tale oggetto il metodo start().

Vediamolo in pratica.

Esempio 1. Estendere Thread

Il modo più diretto per creare un thread è estendere la classe Thread e sovrascriverne il metodo run(). Tutto ciò che scriverai dentro run() verrà eseguito in un thread separato.

// Thread semplice tramite estensione
public class HelloThread extends Thread {
    @Override
    public void run() {
        System.out.println("Ciao dal thread! Mi chiamo: " + getName());
    }
}

public class Main {
    public static void main(String[] args) {
        HelloThread thread = new HelloThread(); // creiamo l'oggetto thread
        thread.start(); // avviamo il thread
        System.out.println("Il thread principale termina l'esecuzione.");
    }
}

Che cosa succede davvero?
Quando chiami thread.start(), Java crea un nuovo thread e avvia il metodo run() all'interno di quel nuovo thread. Nel frattempo il thread principale (quello che parte da main) non aspetta e continua a lavorare in parallelo.

E un avvertimento fondamentale: non confondere start() con la chiamata diretta a run(). Se scrivi thread.run(), non verrà creato alcun nuovo thread — è solo un metodo ordinario che verrà eseguito nello stesso thread in cui lo invochi. Il vero multithreading inizia solo con start().

Che cos'è getName()?

Il metodo getName() restituisce il nome del thread. Per impostazione predefinita Java assegna ai thread nomi del tipo "Thread-0", "Thread-1", ecc. È comodo per il debug.

2. Interfaccia Runnable: il modo migliore nella maggior parte dei casi

Java è un linguaggio che ama la flessibilità. La classe Thread eredita già da Object e in Java non esiste l'ereditarietà multipla tra classi. Se la tua classe estende Thread, non potrà estendere nient'altro (ad esempio, se hai una classe Car e vuoi renderla un thread — dovrai scegliere).

La soluzione: spostare la logica del thread in un oggetto separato che implementa l'interfaccia Runnable. È un'interfaccia con un solo metodo run(). Poi crei un oggetto della classe Thread, gli passi il tuo Runnable — e avvii il thread.

Esempio 2. Classe con Runnable

// Classe che implementa Runnable
public class HelloRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Ciao da Runnable! Thread: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        Runnable runnable = new HelloRunnable();
        Thread thread = new Thread(runnable); // passiamo il Runnable a Thread
        thread.start();
        System.out.println("Il thread principale termina l'esecuzione.");
    }
}

Qui creiamo la classe HelloRunnable, che implementa l'interfaccia Runnable. Nel metodo main viene creato un oggetto di questa classe e passato al costruttore di Thread. Dopodiché il thread viene avviato con la chiamata a start().

Vale la pena menzionare a parte il metodo Thread.currentThread(). È un metodo statico che consente di ottenere l'oggetto del thread corrente e quindi capire esattamente dove viene eseguito il nostro codice.

Perché è meglio?

  • La tua classe può estendere qualsiasi altra classe (non solo Thread).
  • Puoi riutilizzare lo stesso Runnable per avviarlo in più thread.
  • Il codice diventa più flessibile e pulito.

3. Sintassi: classi anonime ed espressioni lambda

Java non resta ferma! Dalla versione 8 possiamo scrivere in modo ancora più conciso e comodo.

Esempio 3. Classe anonima

A volte non ha senso creare un file separato per una classe che serve una sola volta. Puoi dichiararla direttamente nel punto d'uso — si chiama classe anonima.

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable anonimo! Thread: " + Thread.currentThread().getName());
            }
        });
        thread.start();
        System.out.println("Il thread principale termina l'esecuzione.");
    }
}

Esempio 4. Espressione lambda (Java 8+)

L'interfaccia Runnable è funzionale (un solo metodo astratto). Quindi si può usare un'espressione lambda:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread lambda! Thread: " + Thread.currentThread().getName());
        });
        thread.start();
        System.out.println("Il thread principale termina l'esecuzione.");
    }
}

In breve:
Runnable viene implementato «sul posto» e la lambda fornisce l'implementazione del metodo run().

4. Avviare più thread

Si possono avviare quanti thread si vuole (beh, quasi — dipende dalle risorse del computer).

Esempio 5. Avviamo più thread

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            int threadNumber = i; // necessario fare una copia della variabile per la lambda!
            Thread thread = new Thread(() -> {
                System.out.println("Thread #" + threadNumber + ": ciao!");
            });
            thread.start();
        }
        System.out.println("Il thread principale termina l'esecuzione.");
    }
}

L'output può variare!
I thread lavorano in parallelo — quale messaggio appare per primo dipende dal sistema operativo e dallo scheduler dei thread.

5. Ciclo di vita di un thread

Capire come vive un thread è importante per il debug e per usare correttamente i thread.

Stati principali del thread

  • NEW — il thread è stato creato ma non ancora avviato (new Thread(...)).
  • RUNNABLE — il thread è stato avviato (start()) e può essere eseguito.
  • TERMINATED — il thread ha terminato l'esecuzione (il metodo run() è finito).

Cosa succede quando si chiamano start() e run()

  • start() — crea un nuovo thread e chiama il suo metodo run() in quel nuovo thread.
  • run() — è un metodo normale; se lo chiami direttamente, verrà eseguito nel thread corrente (non creerà un nuovo thread!).

Esempio:

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("run() dal thread: " + Thread.currentThread().getName()));
        thread.run();   // VIENE CHIAMATO NEL THREAD PRINCIPALE!
        thread.start(); // VIENE CHIAMATO IN UN THREAD SEPARATO!
    }
}

6. Pratica: realizziamo una semplice applicazione multithread

Continuiamo a sviluppare la nostra applicazione didattica — supponiamo di scrivere un semplice sistema di logging in cui ogni thread scrive il proprio messaggio.

public class LoggerTask implements Runnable {
    private final String message;

    public LoggerTask(String message) {
        this.message = message;
    }

    @Override
    public void run() {
        System.out.println("[" + Thread.currentThread().getName() + "] Log: " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        Thread logger1 = new Thread(new LoggerTask("Avvio del sistema"));
        Thread logger2 = new Thread(new LoggerTask("Caricamento dei dati"));
        Thread logger3 = new Thread(new LoggerTask("Elaborazione della richiesta"));

        logger1.start();
        logger2.start();
        logger3.start();

        System.out.println("Il thread principale termina l'esecuzione.");
    }
}

Che cosa succede?
Tre thread scrivono in parallelo i propri messaggi sulla console. L'ordine dei messaggi non è garantito — questa è la natura del multithreading.

7. Dettagli utili

Tabella: confronto dei modi per creare thread

Metodo Flessibilità Riutilizzo Consigliato?
Estensione di Thread Bassa No Solo per esempi semplici/didattici
Implementazione di Runnable Alta
Classe anonima Media No Per compiti una tantum
Espressione lambda Alta No Sì (Java 8+)

Schema: come funziona l'avvio di un thread

+----------------------+ 
|  main (thread principale)|
+----------------------+
           |
           v
+----------------------+
|  Thread thread = ... |
+----------------------+
           |
           v
+----------------------+
|  thread.start()      |  ← Il thread "prende vita"
+----------------------+
           |
           v
+----------------------+
|  run() nel nuovo thread|
+----------------------+
           |
           v
+----------------------+
|  Thread terminato    |
+----------------------+

8. Errori tipici nell'avvio dei thread

Errore n. 1: chiamare run() invece di start().
Errore molto comune tra i principianti: chiamare direttamente il metodo run() di un oggetto thread. In questo caso non viene creato alcun nuovo thread; il metodo viene semplicemente eseguito nel thread corrente (principale). Corretto: usa sempre start() per avviare un thread.

Errore n. 2: avviare di nuovo lo stesso thread.
Non è possibile chiamare start() più di una volta sullo stesso oggetto Thread — genererà IllegalThreadStateException. Se devi eseguire di nuovo un'attività, crea un nuovo oggetto Thread.

Errore n. 3: modificare variabili condivise senza sincronizzazione.
Se più thread accedono alle stesse variabili, il risultato può essere imprevedibile (race condition). Parleremo di sincronizzazione più avanti; per ora — fai attenzione.

Errore n. 4: non considerare la terminazione dei thread.
Se è importante attendere la fine di un thread, usa il metodo join(). Altrimenti il thread principale potrebbe terminare prima dei thread figli.

Errore n. 5: confondere il nome del thread con il nome della classe.
Il metodo getName() restituisce il nome del thread, non quello della classe. Per il debug è spesso utile assegnare esplicitamente i nomi ai thread tramite il costruttore o setName().

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION