Thread e Priorità in Java

Concetti Chiave
  • In Java, è possibile assegnare priorità ai thread per influenzare l'ordine di esecuzione.
  • Le priorità dei thread vengono utilizzate dallo scheduler di thread per decidere quando ogni thread dovrebbe essere autorizzato a essere eseguito.
  • Le priorità dei thread possono variare da 1 (bassa priorità) a 10 (alta priorità), con un valore predefinito di 5 (priorità normale).
  • È importante cedere il controllo di tanto in tanto per garantire un'esecuzione equa tra i thread, specialmente in ambienti non preemptive.
  • Il metodo setPriority() consente di impostare la priorità di un thread, mentre getPriority() restituisce la priorità corrente del thread.
  • Il metodo yield() permette al thread chiamante di cedere il controllo della CPU, consentendo ad altri thread di essere eseguiti.

Priorità dei Thread

Le priorità dei thread vengono utilizzate dallo scheduler di thread per decidere quando ogni thread dovrebbe essere autorizzato a essere eseguito.

Lo scheduler di thread è il componente del sistema operativo che decide quale thread deve essere eseguito in un dato momento.

In teoria, in un dato periodo di tempo, i thread ad alta priorità ottengono più tempo di CPU rispetto ai thread a bassa priorità. In pratica, la quantità di tempo di CPU che un thread ottiene spesso dipende da diversi fattori oltre alla sua priorità.

Ad esempio, il modo in cui un sistema operativo implementa il multitasking può influenzare la disponibilità relativa del tempo di CPU. Un thread ad alta priorità può anche prevenire che un thread a bassa priorità possa essere eseguito. Ad esempio, quando un thread a bassa priorità è in esecuzione e un thread ad alta priorità riprende (dal sonno o dall'attesa su I/O, per esempio), avrà la precedenza e il thread a bassa priorità verrà sospeso. Questo è noto come preemption o prelazione. Se questo fenomeno si verifica frequentemente, il thread a bassa priorità potrebbe non ottenere mai la possibilità di essere eseguito. Questo fenomeno è noto come starvation.

In teoria, i thread di uguale priorità dovrebbero ottenere uguale accesso alla CPU. Ma bisogna stare attenti. Ricordiamo che Java è progettato per funzionare in una vasta gamma di ambienti. Alcuni di questi ambienti implementano il multitasking in modo fondamentalmente diverso rispetto ad altri.

Per sicurezza, i thread che condividono la stessa priorità dovrebbero cedere il controllo di tanto in tanto (in gergo tecnico questa operazione prende il nome di yield). Questo garantisce che tutti i thread abbiano la possibilità di essere eseguiti sotto un sistema operativo non preemptive. In pratica, anche in ambienti non preemptive, la maggior parte dei thread ottiene comunque la possibilità di essere eseguita, perché la maggior parte dei thread inevitabilmente incontra qualche situazione di blocco, come l'attesa per I/O.

Quando questo accade, il thread bloccato viene sospeso e altri thread possono essere eseguiti. Ma, se si desidera un'esecuzione multithreaded fluida, è meglio non fare affidamento su questo. Inoltre, alcuni tipi di compiti sono intensivi per la CPU. Tali thread dominano la CPU. Per questi tipi di thread, si vuole cedere il controllo occasionalmente in modo che altri thread possano essere eseguiti.

Per impostare la priorità di un thread, utilizzare il metodo setPriority(), che è un membro di Thread. Questa è la sua forma generale:

final void setPriority(int livello)

Qui, livello specifica la nuova impostazione di priorità per il thread chiamante. Il valore di livello deve essere compreso nell'intervallo MIN_PRIORITY e MAX_PRIORITY.

Attualmente, questi valori sono 1 e 10, rispettivamente. Per restituire un thread alla priorità predefinita, specificare NORM_PRIORITY, che è attualmente 5. Queste priorità sono definite come variabili static final all'interno di Thread.

È possibile ottenere l'impostazione di priorità corrente chiamando il metodo getPriority() di Thread, mostrato qui:

final int getPriority()

Le implementazioni di Java possono avere comportamenti radicalmente diversi quando si tratta di pianificazione. La maggior parte delle incoerenze sorge quando si hanno thread che fanno affidamento sul comportamento preemptive, invece di cedere cooperativamente il tempo di CPU. Il modo più sicuro per ottenere un comportamento prevedibile e multipiattaforma con Java è utilizzare thread che cedono volontariamente il controllo della CPU.

Per farlo, è possibile utilizzare il metodo yield() di Thread, mostrato qui:

final static void yield()

Questo metodo consente al thread chiamante di cedere il controllo della CPU. In altre parole, il thread chiamante viene sospeso e altri thread possono essere eseguiti. Il thread chiamante può riprendere l'esecuzione in un momento successivo, a discrezione dello scheduler di thread.