Alan Zucconi > Priority Executor 23 October 2010
Chi ha esperienza di programmazione C o C++ si sarà spesso trovato, se non a dover reinventare la ruota, quantomeno a programmare uno spinterogeno.
La politica di Java su questo è stata chiara fin dall’inizio: una libreria per ogni cosa.
Java non è più un linguaggio. Java È le sue librerie.
Per questo mi ha stupito la totale assenza di un componente che, a mio avviso, sarebbe dovuto essere di serie.
Il package “concurrent“, per quanto non brilli per eleganza e facilità di utilizzo, contiene una miriade di classi dall’utilizzo non ben precisato. Per questo non è stato facilissimo realizzare che quello di cui avevo bisogno non era nascosto tra le classi, ma non c’era proprio!
Seguendo la nomenclatura Java, il componente in questione si chiama “PriorityExecutor“. Alla parola “pool di thread” c’è sempre qualcuno che ha un sobbalzo, e proprio per questo ho deciso di pubblicare un piccolo pacchetto già pronto all’utilizzo.
Immaginatevi di avere un pool di thread. Ed ora immaginatevi che i vostri thread abbiano una proprità. Non mi riferisco alla “Priority” nativa del package “concurrent“, ma al fatto che alcuni debbano essere eseguiti prima degli altri, e per più tempo. Ma ovviamente con l’assunzione che tutti possano passare in esecuzione, evitando spiacevoli deadlock o fenomeni di starvation.
Ecco, questa classe non esiste. O almeno, non esisteva. “PriorityExecutor” realizza proprio quanto descritto, in maniera semplice anche se non sempre elegantissima.
Tutti i thread che vogliono schedulare con questo meccanismo devono estendere la classe “FairyFIFOPriorityRunnable“. Il nome riassume perfettamente le caratteristiche che sono garantite:
- Fairy: tutti i thread passano in esecuzione, evitando deadlock e starvation,
- FIFO: a parità di priorità, la precedenza è data a chi è arrivato per primo,
- Priority: ai thread con priorità maggiore è garantito un accesso più frequente all’esecuzione.
Vediamo come usarla:
class TestThread extends FairyFIFOPriorityRunnable {
/* Costruttore */
public TestThread (final int currentPriority) {
super(currentPriority);
}
/* Il corpo del metodo */
@Override
public void run () {
...
}
}
Sarà sufficiente creare un “PriorityExecutor” e riempirla con un’istanza di questo thread:
/* Due thread possono essere in esecuzione contemporanea */ final PriorityExecutor pool = new PriorityExecutor(2); pool.execute( new TestThread(10) ); pool.execute( new TestThread(5) ); pool.execute( new TestThread(1) );
Il funzionamento è facile. Ad ogni thread è associata una doppia priorità: quella “nominale”, scelta dall’utente, e quella “attuale” che viene incrementata o decrementata automaticamente per garantire le fairyness. Quando un thread è in esecuzione, ci resta fino alla fine del suo “run“. Una volta terminato, la sua priorità attuale viene decrementata. Una volta che la priorità è scesa a zero, viene rimessa al massimo.
Quindi, se un thread ha priorità doppia rispetto ad un altro, verrà mediamente schedulato il doppio delle volte.
È compito dell’utente inserire nuovamente il thread nella lista se vuole che questo concorra di nuovo all’esecuzione. In tal caso comunque, tutte le strutture dati sono già preparate in modo adeguato per gestire il reinserimento ed i cambiamenti di priorità.
Possiamo ottenere questo effetto con facilità sovrascrivendo il metodo “afterExecute” della classe “FairyFIFOPriorityRunnable“:
/* Lo rimette in coda */
@Override
public void afterExecute (final Throwable t) {
/* Senza questa riga, il meccanismo di priorità non funzionerà più! */
super.afterExecute(t);
/* Si rimette in coda */
pool.execute(this);
}
Il pacchetto completo con binari, sorgenti e test può essere scaricato qui: PriorityExecutor .