[Thread] Problème de synchronisation

Le
sylsau
Bonjour,

Je débute avec les threads et j'ai un peu de mal à lire le contenu
d'une variable d'un thread par un autre thread.
Plus précisément, j'ai fait un programme composé de 3 classes :

- une classe player qui exécute un thread pour lire un fichier son via
JavaSound. Dans cette classe j'ai mis un attribut value qui correspond
au pourcentage d'avancement du fichier lu.

- une classe JProgress qui correspond à une ProgressBar qui va lire la
valeur de l'attribut value pour mettre à jour la progressBar. Cette
classe utilise aussi un thread différent et se base sur l'exemple
donné dans la FAQ Java concernant les ProgressBar.

- une classe GUI qui contient un player et une JProgressBar.


Plus précisément voici les parties de mes classes concernées par les
threads :

- classe Player (qui implémente Runnable) :

[CODE]

/**
* méthode run du thread
*/
public void run(){

// on récupère le format de ce fichier
AudioFormat audioFormat = audioInputStream.getFormat();

// on essaie d'ouvrir la ligne nécessaire à la lecture.
try
{
line.open(audioFormat);
}
catch (LineUnavailableException e)
{
e.printStackTrace();
System.exit(1);
}
catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}

// on démarre la ligne, ce qui permettra au flux d'être redirigé
// sur la carte Son
line.start();

// on récupère les données à lire
ArrayList<byte[]> listData = getDataOutput();

int j = 0, k = 0;

// on lit chacune d'entre elles
for(byte[] part : listData){

int taille = sizes.get(j);

for(int i = 0; i < taille; i+=20){

/* C'EST ICI QUE SE FAIT L'ACCESS à l'attribut value de player */
value = k * 100 / size;
line.write(part, i, 2);
k+=20;

}

//line.write(part, 0, part.length);
j++;
}

// on s'assure de bien vider les parties restant à lire
line.drain();
// fermeture de la ligne
line.close();

}[/CODE]

- classe JProgress :

[CODE]

public class JProgress extends JProgressBar implements Runnable
{

/**
*
*/
private static final long serialVersionUID = 1L;
private boolean isStarted;
private int value;

/**
* Player associé à la progressBar
*/
private Player player;

public JProgress(Player player){

// construction de la JProgressBar
super(0,100);

this.player = player;
this.isStarted = false;
this.value = 0;
setStringPainted(true);

}

public void setValue(int value){
this.value = value;
}

public void setStarted(boolean started){
isStarted = started;
}

public void launch(){

// si progressBar non démarée
if(!isStarted){

isStarted = true;
Thread t = new Thread(this);
t.start();
}
}


// methode de l'interface Runnable
// lance un nouveau thread qui va executer le code de la methode
longTraitement
public void run(){

/* ICI ON TENTE D'ACCEDER à l'attribut value du player associé via un
getter */
while(player.getValue() < 100)
majProgress();

}

// methode qui met a jour la JProgressBar par le processus
d'evenement
// Pourquoi obliger l'execution de cette methode par le processus
d'evenement ?
// -> Cf : la docs du tutoriel de Sun section : "Threads and Swing"
// http://java.sun.com/docs/books/tutorial/uiswing/mini/threads.html
public void majProgress ()
{ if ( SwingUtilities.isEventDispatchThread () )
{
/* ON TENTE DE MODIFIER LA VALEUR DE la progressbar en accédant à
l'attribut value du player associé */
setValue(player.getValue());
}
else
{ Runnable callMAJ = new Runnable ()
{ public void run ()
{ majProgress ();
}
};
SwingUtilities.invokeLater (callMAJ);
}
}
}

[/CODE]

- classe GUI (je mets juste la méthode qui déclenche les 2 threads en
gros) :

[CODE]

public void play(){

if(player != null && player.getFile() != null){
// on lance la lecture sur le player
player.play();
// on lance la progress bar
progress.launch();
}
}

[/CODE]


Donc voilà, en lançant mon code la progressBar reste bloquée à 0%. =
Il
y a évidemment quelque chose que j'ai mal fait parce qu'une fois le
thread de lecture fini la progressbar ne se met même pas à jour à 100%
côté du thread de JProgress bien que la valeur de value soit à 100 =
à
ce moment là.

Quelqu'un pourrait-il m'aider et m'aiguiller pour résoudre ce
problème ?

Merci d'avance de votre aide.
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses Page 1 / 2
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Black Myst
Le #227333
Bonjour,

Je débute avec les threads et j'ai un peu de mal à lire le contenu
d'une variable d'un thread par un autre thread.
Plus précisément, j'ai fait un programme composé de 3 classes :

- une classe player [...]
- une classe JProgress [...]
- une classe GUI qui contient un player et une JProgressBar.

[...]
Donc voilà, en lançant mon code la progressBar reste bloquée à 0%. Il
y a évidemment quelque chose que j'ai mal fait parce qu'une fois le
thread de lecture fini la progressbar ne se met même pas à jour à 100%
côté du thread de JProgress bien que la valeur de value soit à 100 à
ce moment là.



Ma première remarque concerne la façon dont tu appel SwingUtilities pour
faire exécuter ta mise à jour par le thread AWT. si on simplifie un peu
ton code, on obtient ca :

while( val<100 ) {
SwingUtilities.invokeLater( }

Je pense qu'il est possible que le thread poste plusieurs centaines (de
millier) de tache à Swing avant que Swing n'ai le temps de comprendre ce
qui lui arrive... Quoi qu'il arrive, tu va envoyer plus vite tes
demandes que Swing ne peut les gérer et écrouler/freezer la machine.

En remplacant l'appel à invokeLater par l'appel invokeAndWait(), ton
thread ne pourra surcharger Swings comme un âne, mais tu risque d'avoir
tout de même un CPU à 100% pour rien. On ajoute en général une tempo
minime de quelque ms pour libérer le CPU.

while( val<100 ) {
SwingUtilities.invokeAndWait( // Une petite pose de 100ms...
Thread.sleep(100);
}

Voila pour ce qui concerne la synchro avec le Thread AWT...
Il nous reste maintenant à synchroniser entre eux tes 2 threads.

Tu peux, au choix :

- Utiliser le mot clé 'volatil' à la déclaration de ta variable 'value':
public volatil int value=0;
De cette manière, tu indique à la JVM que cette variable est utilisé par
plusieur thread. Ce type de synchro n'est valable que si tu as 1 seule
variable en commun...

- Utiliser le mot clé 'synchronized' devant tes méthodes setValue() et
getValue(), de cette manière, la JVM te garantie qu'a un moment donné,
un seul thread ne peut accéder à ta variable.


Avec ça, tu devrais voir bouger ta barre de progression.
cdl
Black Myst

remy
Le #227331
bonjour

en gros et pour faire simple :-)

******
package util;
import java.util.EventListener;

public interface ThreadListener extends EventListener {
public void FinThread(ThreadEvent e);

}
*********
package util;

public class ThreadEvent {

private Runnable obj;
public ThreadEvent(Runnable obj)
{
this.obj=obj;
}
public Runnable getRunnable()
{
return obj;
}
}
***************
package util;

import javax.swing.event.EventListenerList;

public class UtilThreadListener
{
private final EventListenerList listeners = new EventListenerList();

public void addThreadListener(ThreadListener listener)
{
listeners.add(ThreadListener.class, listener);
}

public void removeThreadListener(ThreadListener listener)
{
listeners.remove(ThreadListener.class, listener);
}
public ThreadListener[] getThreadListener()
{
return listeners.getListeners(ThreadListener.class);
}
protected void threadChanged(ThreadEvent e)
{
for(ThreadListener listener : getThreadListener())
{
if(listener!=null)
{
listener.FinThread(e);
}
}
}
}
**************

public class Essai extends UtilThreadListener implements Runnable
{

...
public void run()
{
...
threadChanged(new ThreadEvent(this));
}
}
*************
public class Gestion implements ThreadListener

Gestion()
{
Essai e=new Essai ();
e.addThreadListener(this);
}


public void FinThread(ThreadEvent e)
{
if(e.getRunnable() instanceof Essai)
...
}


a+ remy
sylsau
Le #227330
Merci de votre réponse tout d'abord.

En remplacant l'appel à invokeLater par l'appel invokeAndWait(), ton
thread ne pourra surcharger Swings comme un âne, mais tu risque d'avoir
tout de même un CPU à 100% pour rien. On ajoute en général une te mpo
minime de quelque ms pour libérer le CPU.


En utilisant invokeAndWait(), il faut rajouter les exceptions à la
méthode appelante. Or, dans mon programme forcément invokeAndWait sera
appelé dans un run qui est un méthode implémentée dans l'interface
Runnable et dont la signature n'inclut pas ce lancement d'exceptions
(InterruptedException et InvocationTargetException pour être plus
précis).

Donc, je ne vois pas trop comment utiliser invokeAndWait.

J'ai essayé ensuite les 2 méthodes que vous m'avez proposé (avec
volatile et ensuite avec synchronized en rajoutant setValue dans le
player pour éviter l'accès direct à value) mais dans les 2 cas, cela
ne change rien à ma barre de progression qui reste à 0%.

Cela vient il du fait que je n'ai pas pu utiliser invokeAndWait ?

PS : Si vous voulez, je peux vous poster le code entier des 3 classes
pour que testiez directement.

sylsau
Le #227328
On 27 mar, 09:25, remy
bonjour

en gros et pour faire simple :-)



Heureusement lol.

Plus sérieusement, en gros ça permet de faire communiquer les threads
entre eux.
Mais en lisant comme ça, j'ai l'impression que ça me permettra juste
de récupérer la valeur de la classe s'exécutant dans un premier thread
et ce depuis un second thread mais seulement quand le premier sera
fini.

Ce que j'aimerais c'est pouvoir récupérer les valeurs de l'attribut de
la classe player du premier thread au fur et à mesure de son évolution
dans ma classe se situant dans un second thread et ceci pour que ma
barre de progression avance bien au fur et à mesure de l'évolution de
la lecture dans le player.

remy
Le #227315
On 27 mar, 09:25, remy
bonjour

en gros et pour faire simple :-)



Heureusement lol.

Plus sérieusement, en gros ça permet de faire communiquer les threads
entre eux.
Mais en lisant comme ça, j'ai l'impression que ça me permettra juste
de récupérer la valeur de la classe s'exécutant dans un premier thread
et ce depuis un second thread mais seulement quand le premier sera
fini.

Ce que j'aimerais c'est pouvoir récupérer les valeurs de l'attribut de
la classe player du premier thread au fur et à mesure de son évolution
dans ma classe se situant dans un second thread et ceci pour que ma
barre de progression avance bien au fur et à mesure de l'évolution de
la lecture dans le player.


oui dit differemmant

c'est le thread qui execute le code dans le listener FinThread(..
sinon c'est plus delicat ou long il faut mettre la class
UtilThreadListener dans un thread independant, changer un etat dans la
classe UtilThreadListener et le reveiller
son reveil endort l'instance qui l'a reveillé, s'excecute et reveille
l' instance qui la endormie puis le thread UtilThreadListener se réendort

mais cela ne sert pas a grand chose puisque les swing s'executent dans
son propre thread se qui revient plus ou moins a la meme chose tu
changes ton dialogue
et tu le forces a se redessiner en gros

sinon je ne suis pas sur d'etre tres clair


public class Essai extends UtilThreadListener implements Runnable
{
int val=0
.
public void run()
{
for (int i=0;i++;i<10)
{

threadChanged(new ThreadEvent(this));

}
}

*************
public class Gestion implements ThreadListener

Gestion()
{
Essai e=new Essai ();
e.addThreadListener(this);
}


public void FinThread(ThreadEvent e)
{
if(e.getRunnable() instanceof Essai)
{
System.out.println(e.getRunnable().val);

}
}


sylsau
Le #227314
Bon en fait, en mettant un bloc try ... catch autour de invokeAndWait
ça compile normalement, j'avais oublié :).

Bon sinon, là j'ai testé en mettant aussi un sleep. Pour être sur que
le problème ne venait pas de ma barre de progression j'ai testé de
mettre juste un affichage sur la sortie standard dans le majProgress
et là effectivement ça marche j'ai bien un affichage des différents
pourcentages du player.

Donc finalement mon problème vient de ma progressBar qui ne se met pas
à jour correctement.

Savez vous d'où ça peut venir ? J'ai testé en mettant un
update(getGraphics()); dans le majProgress mais ça ne change en rien
sur la valeur de la progressBar.
Zazoun
Le #227313
ton problème ne viendrait-il pas tout simplement du fait que tu
surcharges la méthode setValue de JProgressBar ?
Black Myst
Le #227311
Merci de votre réponse tout d'abord.
(...)
Cela vient il du fait que je n'ai pas pu utiliser invokeAndWait ?
Tout le problème de gérer de la synchronisation, que ce soit en java ou

dans un autre language, c'est que le comportement d'un algorithme mal
synchronisé est très très difficile à prévoir.

Le comportement d'un algorithme mal synchroniser peut dépendre :
- du compilateur (et de ces options d'optimisation)
- de la JVM (Plus encore si un GIT passe de temps en temps)
- de l'OS (suivant la façon dont il gère les thread)
- de la charge de la machine
- de la chance

Je ne peux donc pas te dire que le invokeAndWait corrigera le problème.

PS : Si vous voulez, je peux vous poster le code entier des 3 classes
pour que testiez directement.
Si j'étais vraiment motivé pour réinstaller un environnement de

développement Java sur ma machine perso, j'arriverais aussi à refaire
tes 3 classes sans trop de problème...
J'aime bien lire les news après une journée de boulo, mais il est exclus
de relancer Eclipse.

++
Black Myst

Black Myst
Le #227310
Bon en fait, en mettant un bloc try ... catch autour de invokeAndWait
ça compile normalement, j'avais oublié :).
lol


sylsau
Le #227309
On 27 mar, 17:17, "Zazoun"
ton problème ne viendrait-il pas tout simplement du fait que tu
surcharges la méthode setValue de JProgressBar ?


Effectivement c'était bien ça le problème ! J'ai perdu pas mal de
temps et je vous en ai fait perdre aussi d'ailleurs :( lol.

Merci en tous cas.

Publicité
Poster une réponse
Anonyme