View Full Version : [JAVA] Worker thread, giusto così?
Salve,
devo fare un update di un componente swing - setText o setIcon - con dei
dati che impiego un po' a calcolare, e quindi devo tirare via la computazione
dall'EDT, pena "freeze" della gui.
C'è anche da dire che se durante l'esecuzione del worker thread ne lancio un altro
voglio che sia quest'ultimo ad aggiornare il componente.
In pratica le vecchie computazioni non mi interessano.
Ho impostato la classe così:
public class IDTextUpdater
implements Runnable
{
...
private static Thread lastThread;
...
/* qui sono nel costruttore */
// update last thread variable
lastThread = new Thread(this);
lastThread.start();
/* fine costruttore */
/* nel metodo run()... */
// lavora...
...
if( lastThread != Thread.currentThread() )
return;
// aggiorna il componente
SwingUtilities.invokeLater( new Runnable() {...} )
/* fine run() */
Il tutto sembra funzionare egregiamente, ma siccome con la concorrenza
non si sa mai, vedete qualcosa che potrebbe non andare?
Grazie.
se la computazione è grossa nel costruttore potresti mettere uno stop thread(nn ricordo se è deprecato, cmq bloccarlo in qualche maniera).
Cmq se per caso viene creato un nuovo updater, e il vecchio è bloccato dopo il controllo if( lastThread != Thread.currentThread() ), e il nuovo updater finisce prima che il controllo ritorni al vecchio updater, puoi trovarti con la textbox valorizzata col risultato del vecchio updater.
Grazie della risposta.
Sì, stop è deprecato, il modo pulito per terminare un thread è uscire dal metodo run().
Per il resto forse hai ragione, ma d'altra parte non saprei come fare diversamente...
Forse mettendo statici i componenti da aggiornare e mettendoli a null
nel costruttore? Il tutto sincronizzato ovviamente.
lastThread deve essere volatile o incapsulato in un AtomicReference.
Come indicato da thebol, il programma che hai scritto non fa quello che vorresti (eseguire solo l'ultimo aggiornamento grafico). Tra le possibili soluzioni io vedrei bene una doppia coda di consumazione di eventi Runnable. Una per le computazioni in background, una per gli aggiornamento grafici. Quando immetti una computazione, che potrebbe essere rappresentata con;
interface MultiTask {
Runnable getBackgroundTask();
Runnable getAWTTask();
}
infili nella coda dei compiti in background il prodotto di getBackgroundTask(), svuoti la coda dei compiti AWT e immetti il prodotto di getAWTTask(). L'uso di una coda (LinkedBlockingQueue) garantisce la sequenzialità dell'esecuzione di compiti AWT non più annullabili (già offerti prima di un clear()). Lo stesso tipo di struttura dati per i compiti in background ti permette di delegare l'esecuzione degli stessi ad un pool di Thread, idealmente più efficiente di un Thread per computazione.
Ok, ci lavoro su e vi faccio sapere.
Allora, per i thread pool e le code bloccanti vedrò più avanti,
magari li userò per un altra cosetta che ho in mente.
Intanto ho modificato il codice così:
private static IDTextUpdater lastUpdater; // tolto lastThread
private boolean stopped;
...
/* qui sono nel costruttore */
this.stopped = false;
if( lastUpdater != null )
lastUpdater.stop();
lastUpdater = this;
new Thread( this ).start();
/* fine costruttore */
void stop()
{
stopped = true;
}
/* nel metodo run()... */
// lavora...
...
if( stopped )
return;
SwingUtilities.invokeLater( new Runnable()
{ if( !stopped ) /* update componente*/...} );
/* fine run() */
Che ne dite?
^TiGeRShArK^
26-02-2007, 18:12
e xkè non usare SwingWorker anzikè reinventare la ruota? :fagiano:
ehm...
ovviamente se epuoi usare java 6 :p
Devo usare java5, e comunque non risparmierei una riga di codice...
non potendo essere final, lastUpdater e stopper devono essere volatile o incapsulati in un AtomicReference/AtomicBoolean o il loro accesso deve essere contenuto in un blocco sincronizzato.
non potendo essere final, lastUpdater e stopper devono essere volatile o incapsulati in un AtomicReference/AtomicBoolean o il loro accesso deve essere contenuto in un blocco sincronizzato.
*
in quella maniera hai solo spostato piu avanti nel codice il controllo, ma non l'hai eliminato o reso sincronizzato rispetto a piu thread.
ps che roba sono le atomicQualcosa?
Sono i nuovi volatile. Se prima si diceva:
private volatile boolean pimpumpam...
oggi si dovrebbe prediligere:
private final AtomicBoolean pimpumpam...
C'è un Atomic per tipo, con AtomicReference asso piglia tutto.
Credo che il problema d'ordine sia stato risolto. Quel booleano, uno per ogni Thread, viene controllato in un unico Thread, l'AWT Event Dispatcher. Poichè il Runnable è premuto nella coda degli eventi AWT, esiste una relazione d'ordine tra gli eventi.
Se il Thread è bloccato nell'esecuzione del runnable allora non può verificarsi l'aggiornamento grafico del Thread successivo perchè il Thread successivo ha premuto il suo evento nella stessa coda.
Se l'evento del Thread precedente è premuto nella coda prima di quello del Thread successivo, allora quando sarà eseguito il runnable del Thread precedente il valore di stopped sarà necessariamente (volatile o Atomic) true.
Insomma, mi pare che funzioni. A naso e alla 22:00, ma direi che funziona. Lì. Quello che mi lascia perplesso è "lastUpdater".
E' possibile che un Thread a cui è affidata l'esecuzione di un aggiornamento precedente arrivi a bloccare un Thread a cui è affidata l'esecuzione dell'aggiornamneto successivo.
io eseguo:
thread 1 (aggiornamento ui vecchio) -> via
thread 2 (aggiornamento ui nuovo) -> via
thread 1 sta per eseguire: lastUpdater.stop
thread 2 tira la volata e arriva fino a lastUpdater = this
thread 1 esegue lastUpdate.stop e impedisce l'aggiornamento UI di Thread 2
L'ultimo aggiornamento grafico non viene eseguito.
O mi sono perso qualcosa?
Credo che il problema d'ordine sia stato risolto. Quel booleano, uno per ogni Thread, viene controllato in un unico Thread, l'AWT Event Dispatcher. Poichè il Runnable è premuto nella coda degli eventi AWT, esiste una relazione d'ordine tra gli eventi.
Se il Thread è bloccato nell'esecuzione del runnable allora non può verificarsi l'aggiornamento grafico del Thread successivo perchè il Thread successivo ha premuto il suo evento nella stessa coda.
Se l'evento del Thread precedente è premuto nella coda prima di quello del Thread successivo, allora quando sarà eseguito il runnable del Thread precedente il valore di stopped sarà necessariamente (volatile o Atomic) true.
Insomma, mi pare che funzioni. A naso e alla 22:00, ma direi che funziona. Lì.
si avevo letto troppo velocemente il codice, mi sa che hai ragione tu
Quello che mi lascia perplesso è "lastUpdater".
E' possibile che un Thread a cui è affidata l'esecuzione di un aggiornamento precedente arrivi a bloccare un Thread a cui è affidata l'esecuzione dell'aggiornamneto successivo.
io eseguo:
thread 1 (aggiornamento ui vecchio) -> via
thread 2 (aggiornamento ui nuovo) -> via
thread 1 sta per eseguire: lastUpdater.stop
thread 2 tira la volata e arriva fino a lastUpdater = this
thread 1 esegue lastUpdate.stop e impedisce l'aggiornamento UI di Thread 2
L'ultimo aggiornamento grafico non viene eseguito.
O mi sono perso qualcosa?
mi sa che hai ragione...
...tanto per far capire quanto sia complicato giocare con i thread
Ma ragazzi, il costruttore, in cui chiamo lastUpdater.stop(), viene eseguito nell'EDT,
che e' un unico thread, quindi sequenzialmente, e non parallelamente, o sbaglio?
lastUpdater non e' un thread, e' un Runnable eseguito dal thread.
Se lastUpdater ha un metodo stop è un po' più di un Runnable. Che non sia un Thread l'ho capito :D.
Se il costruttore è eseguito dall'AWT Event Dispatcher allora, contrariamente al classico libro giallo, la trama si semplifica.
Il caso che mi lasciava perplesso non si verifica più. lastUpdater non deve essere volatile/atomic: è usato da un solo Thread dunque che sia aggiornata la sola copia locale di quel campo è indifferente.
Deve ancora essere volatile/atomic "stopped", perchè il suo valore è letto da un Thread e scritto da un altro.
Ma l'assegnamento a un boolean e' comunque atomico... o no?
E' un inghippo terminologico. Sì, l'assegnamento è atomico, nello stesso senso in cui non è atomico l'assegnamento ad un long.
Ma se portiamo la faccenda in un contesto concorrente, questa atomicità non è più sufficiente. I thread operano su copie locali dei campi. Tutte le operazioni che un Thread esegue su un campo sono operazioni il cui effetto è limitato alla copia locale di quel campo. Così se in un Thread assegno al campo ciccio il valore N ciò che accade, secondo le specifiche del linguaggio, è che la copia locale del campo ciccio, che esiste solo per quel tal Thread, assume il valore N. Il campo "vero", quello che esiste nell'heap, resta inalterato. Lo stesso vale per la lettura. Quando un Thread legge il valore di un campo legge il valore della copia locale di quel campo.
I valori iniziali dei campi usati dai Thread sono quelli esistenti nell'heap. Poichè ogni volta che un Thread muta il valore di un campo tale mutazione è propria della sola copia locale di quel campo, in linea di principio i campi hanno sempre e solo il valore iniziale predefinito (null per i reference, 0 per i numerici, '\0' per i caratteri, false per i boolean).
Ci sono poi una serie di norme che stabiliscono quando la manipolazione che un Thread opera su un campo risulta visibile ad altri Thread. Una di queste coinvolge l'uso del modificatore volatile (e del suo alter-ego in Java 5 rappresentato dagli AtomicBlaBla). Detto terra-terra, quando assegni un valore ad un campo volatile quel valore finisce nell'heap e sarà quindi il valore ottenuto da altri Thread quando accederanno a quel campo.
Nel nostro caso noi ci chiediamo: qual'è il valore del campo "stopped". Basandoci sulle norme del modello di memoria del linguaggio di programmazione Java noi sappiamo che.
1. stopped ha sicuramente il valore di inizializzazione predefinito "false".
2. nel costruttore quel valore cambia ma quello che veramente cambia è il valore di una copia locale (ad un Thread) di stopped.
3. nel metodo run, il valore certo di stopped è false se il run sia eseguito da un Thread diverso da quello che ha invocato il costruttore.
4. nel metodo run del Runnable usato in invokeLater, il valore certo di stopped è ancora false se il costruttore non sia stato eseguito dall'AWT Event Dispatcher. E' true se, come nel nostro caso, il costruttore sia stato eseguito dall'AWT Event Dispatcher.
Ecco perchè dico che "stopped" deve essere volatile: perchè il metodo run è certamente eseguito da un Thread diverso dall'AWT Event Dispatcher.
Non serve che sia volatile "lastUpdated". In questo caso, infatti, hai garantico che il costruttore, in cui il valore di lastUpdated cambia, sarà eseguito sempre e soltanto da un solo Thread. Questo Thread lavorerà sulla sua copia locale e questa copia locale assumerà di volta in volta valori diversi.
Una curiosita': questa storia delle variabili locali ai thread vale anche per le statiche?
a naso direi di si, ma aspetterei il guru per la conferma ;)
Mentre aspettiamo il guru, voto anch'io per il sì.
Mentre aspettiamo il guru, voto anch'io per il sì.
:mc: :sofico:
:Prrr:
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.