Java - Thread Scheduler

Ciao a tutti, futuri maghi Java! Oggi ci imbarcheremo in un viaggio entusiasmante nel mondo della pianificazione dei thread in Java. Non preoccupatevi se siete nuovi alla programmazione – sarò il vostro guida amichevole e affronteremo questo argomento passo per passo. Allora, afferrate le vostre bacchette virtuali (tastiere) e immergetevi!

Java - Thread Scheduler

Cos'è la Pianificazione dei Thread?

Prima di entrare nei dettagli, diamo un'occhiata a cosa significhi la pianificazione dei thread. Immagina di essere un direttore di circo (che è il runtime di Java) e avere più artisti (thread) in attesa di mostrare i loro numeri. Il tuo compito è decidere quale artista va in scena e per quanto tempo. Questo è essenzialmente quello che fa la pianificazione dei thread in Java – gestisce più thread e decide quale deve essere eseguito e quando.

Perché Abbiamo Bisogno della Pianificazione dei Thread?

Potreste chiedervi: "Perché non lasciamo semplicemente eseguire tutti i thread quando vogliono?" Beh, immagina se tutti gli artisti del circo cercassero di esibirsi contemporaneamente – sarebbe il caos! La pianificazione dei thread aiuta a mantenere l'ordine, garantisce un'equa distribuzione delle risorse e migliora le prestazioni complessive del sistema.

Il Pianificatore dei Thread di Java

Java ha un pianificatore di thread integrato che gestisce questo compito complesso per noi. Utilizza una combinazione di pianificazione a livello di sistema operativo e dei suoi algoritmi per gestire i thread in modo efficiente.

Stati dei Thread

Prima di entrare nella pianificazione, ripassiamo rapidamente gli stati diversi in cui può trovarsi un thread:

  1. New
  2. Runnable
  3. Running
  4. Blocked/Waiting
  5. Terminated

Il pianificatore è responsabile del movimento dei thread tra questi stati.

Creazione e Pianificazione di Thread di Base

Iniziamo con un esempio semplice per vedere come Java crea e pianifica i thread:

public class SimpleThreadExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 1: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread 2: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

thread1.start();
thread2.start();
}
}

In questo esempio, creiamo due thread che stampano numeri da 0 a 4. La chiamata Thread.sleep(1000) fa sì che ciascun thread si ferma per 1 secondo tra le stampe.

Quando esegui questo programma, noterai che l'output potrebbe non essere in ordine perfettamente alternato. Questo perché il pianificatore di Java decide quando passare da un thread all'altro!

Priorità dei Thread

Java ci permette di dare suggerimenti al pianificatore su quali thread sono più importanti. Possiamo impostare le priorità dei thread utilizzando il metodo setPriority(). Modifichiamo il nostro esempio precedente:

public class ThreadPriorityExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread di Bassa Priorità: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread di Alta Priorità: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);

thread1.start();
thread2.start();
}
}

In questo esempio, impostiamo thread1 con la priorità più bassa e thread2 con la priorità più alta. Anche se questo non garantisce che thread2 venga sempre eseguito per primo o più spesso, dà al pianificatore un suggerimento che dovrebbe preferire thread2 quando possibile.

ScheduledExecutorService

Ora, diamo un'occhiata a un modo più avanzato per pianificare i thread utilizzando ScheduledExecutorService. Questo strumento potente ci permette di pianificare task da eseguire dopo un ritardo o a intervalli fissi.

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

Runnable task1 = () -> System.out.println("Task 1 eseguito alle: " + System.currentTimeMillis());
Runnable task2 = () -> System.out.println("Task 2 eseguito alle: " + System.currentTimeMillis());

executor.schedule(task1, 5, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(task2, 0, 2, TimeUnit.SECONDS);

try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}

executor.shutdown();
}
}

In questo esempio:

  • Creiamo un ScheduledExecutorService con 2 thread.
  • task1 è pianificato per essere eseguito una volta dopo un ritardo di 5 secondi.
  • task2 è pianificato per essere eseguito ogni 2 secondi, partendo immediatamente.
  • Lasciamo il programma running per 10 secondi prima di chiudere l'esecutore.

Questo dimostra come possiamo controllare precisamente quando e quanto spesso i nostri task vengono eseguiti.

Metodi di ScheduledExecutorService

Ecco una tabella dei principali metodi forniti da ScheduledExecutorService:

Metodo Descrizione
schedule(Runnable, long, TimeUnit) Pianifica un task una tantum da eseguire dopo un ritardo specificato
scheduleAtFixedRate(Runnable, long, long, TimeUnit) Pianifica un task per essere eseguito periodicamente, con un ritardo fisso tra l'inizio di ciascuna esecuzione
scheduleWithFixedDelay(Runnable, long, long, TimeUnit) Pianifica un task per essere eseguito periodicamente, con un ritardo fisso tra la fine di una esecuzione e l'inizio della successiva

Conclusione

Ed eccoci qua, ragazzi! Abbiamo attraversato i fondamenti della pianificazione dei thread in Java, dalla creazione semplice dei thread all'advanced scheduling con ScheduledExecutorService. Ricorda, la pianificazione dei thread è come condurre un'orchestra – si tratta di armonia e tempismo.

Man mano che continui la tua avventura Java, scoprirai ancora più modi per ottimizzare il comportamento dei thread. Ma per ora, datti una pacca sulla spalla – hai fatto un grande passo nel mondo della programmazione concorrente!

continua a practicing, mantieniti curioso e, più importante, divertiti a programmare! Chi sa, forse un giorno scriverai la prossima generazione di pianificatori di thread Java. Fino a quel momento, buona threading!

Credits: Image by storyset