IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Java Discussion :

L'utilisation de synchronized dans une application multi-thread


Sujet :

Java

  1. #1
    Membre du Club Avatar de Tigrounette
    Profil pro
    Inscrit en
    Décembre 2005
    Messages
    132
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2005
    Messages : 132
    Points : 69
    Points
    69
    Par défaut L'utilisation de synchronized dans une application multi-thread
    Salut ici,

    Je vous explique mon problème :

    J'ai un jeu multijoueur qui utilise une application java comme serveur. Donc, à chaque fois qu'un joueur se connecte, un nouveau thread est créé.

    Le problème, c'est que quand j'arrive vers 30-40 joueurs, de temps en temps le serveur n'envoie plus rien à personne pendant environs 10 secondes. Des gros coup de lag en gros ^^

    Ca c'est quand je n'utilise pas le mot clef "synchronized".

    Le problème, c'est quand je met synchronized devant toutes mes méthodes le serveur plante (sans message d'erreur) ou bout de 4-5 joueurs.

    C'est un petit jeu de plateforme multijoueur en temps réel que vous pouvez trouvez ici : http://www.extinction.fr/aaaaah/. C'est assez dynamique et le serveur reçoit pas mal d'informations par secondes.

    Je n'arrive pas à trouver où ça coince car le serveur ne m'envoie pas de message d'erreur.

    J'ai l'impression que ça vas pas lorsque le serveur doit redistribuer un message à plusieurs client. Il procède de cette façon :

    Cette méthode se trouve dans la classe principale, LS_JOUEUR correspondant à un ArrayList contenant le thread de chaque joueur. Cet objet est le seul que les threads se "partage".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    public void Envoie(ArrayList<Joueur> LS_JOUEUR, String MESSAGE) {
    	int NbJoueur = LS_JOUEUR.size();
    	for (int i = 0; i < NbJoueur; i++) {
    		LS_JOUEUR.get(i).Envoie(MESSAGE);
    	}
    }
    Dans le thread de chaque joueur se trouve cette méthode, qui est appelée plus haut :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    public void Envoie(String MESSAGE) {
    	Socket_Out.print(MESSAGE + '\u0000');
    	Socket_Out.flush();
    }
    Si quelqu'un peut m'indiquer comment placer mes mot clef synchronized, ça m'intéresse

  2. #2
    Membre confirmé
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    731
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 731
    Points : 574
    Points
    574
    Par défaut
    Le problème, c'est quand je met synchronized devant toutes mes méthodes le serveur plante (sans message d'erreur) ou bout de 4-5 joueurs.
    Disons que l'utilisation du mot-clé synchronized est coûteuse vu que le runtime bloque ce qui est synchronisé par tous les thread l'utilisant. Alors si toutes tes méthodes sont synchronisées et souvent appelées ; cela ne m'étonne pas que cela parte en choucroute.

    Si quelqu'un peut m'indiquer comment placer mes mot clef synchronized, ça m'intéresse
    Uniquement sur les objets qui sont partagés par plusieurs threads.

    Si LS_JOUEUR est un ArrayList de Thread, ce n'est pas lui qu'il faut synchroniser mais les données que ces threads se partagent.

  3. #3
    Membre expert
    Avatar de natha
    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    2 346
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Janvier 2006
    Messages : 2 346
    Points : 3 083
    Points
    3 083
    Par défaut
    Tes threads de joueurs font de l'attente active (while 1, ...) ? ou passive (sleep, wait, ...) ?
    S'ils font de l'attente active ça peut poser des problèmes car ils sont tout le temps actif ce qui signifie que quand tu en a 40, il est plus difficile de "partager" leur activité.
    Comment ça ? La réponse à ton problème n'est ni dans la faq, ni dans les tutos, ni dans sources ??? Etonnant...
    De la bonne manière de poser une question (et de répondre).
    Je ne fais pas de service par MP. Merci (...de lire les règles...).
    Ma page dvp.com

  4. #4
    Membre éclairé Avatar de LeXo
    Profil pro
    Inscrit en
    Janvier 2004
    Messages
    1 147
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Janvier 2004
    Messages : 1 147
    Points : 868
    Points
    868
    Par défaut
    je pense que l'on préfère l'utilisation de l'API fournit par concurrent que l'utilisation du synchronized
    Plzzz pas de questions par MP.

  5. #5
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    132
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2007
    Messages : 132
    Points : 170
    Points
    170
    Par défaut
    Il est sympa ce petit jeu

    Comment est-ce que les threads reçoivent les messages?

    Si je comprends tu partages la structure LS_JOUEUR entre tous tes threads.

    Et donc pour envoyer un message a un thread tu dois mettre ton message dans une liste et ton thread doit le lire non ?

    Est-ce que tu peux nous fournir le code de envoie et le code de la lecture des messages par tes threads ?

    Sinon pour eviter d'avoir à synchronizer ta liste LS_JOUEUR pourquoi ne pas avoir une queue de message par Joueur et lorsque le serveur veut envoyer un message à un client il le poste dans la queue de message?

  6. #6
    Membre du Club Avatar de Tigrounette
    Profil pro
    Inscrit en
    Décembre 2005
    Messages
    132
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2005
    Messages : 132
    Points : 69
    Points
    69
    Par défaut
    Alors, dans la méthode run() de chaque client j'ai ce code qui permet de recevoir et de lire les messages :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    try {
    	StringBuilder Compacteur = new StringBuilder(100);
    	char Caractere[] = new char[1];
    	while (Socket_In.read(Caractere, 0, 1) != -1) {
    		char C = Caractere[0];
    		if (C == 0) {
    			if (Compacteur.length() != 0) {
    				String Message = Compacteur.toString();
    				Compacteur = new StringBuilder(100);
    				Analyse_Message(Message);
    			}
    		} else {
    			Compacteur.append(C);
    		}
    	}
    	Deconnexion("Fermeture du client");
    } catch (SocketTimeoutException ste) {
    	Deconnexion("Connexion perdue");
    } catch (Exception e) {
    	Deconnexion("Deconnexion du client");
    	//e.printStackTrace();
    }
    LS_JOUEUR fait en fait toujours référence au même arrayList qui se trouve dans la classe principale et qui contient tous les thread des clients.

    En fait ça fonctionne comme ça :

    Le serveur reçoit un message qu'il doit redistribuer à tous les clients. Il prend donc l'ArrayList qui contient tous les Clients, il parcourt cette liste et utilise cette méthode qui se trouve dans la classe de chaque client :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    public void Envoie(String MESSAGE) {
    	Socket_Out.print(MESSAGE + '\u0000');
    	Socket_Out.flush();
    }
    Socket_Out est définie dans le constructeur de chaque client :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    public Joueur(Socket SOCKET, Serveur SERVEUR) {
    	Serveur = SERVEUR;
    	Socket = SOCKET;
    	AdresseIP = Socket.getInetAddress().toString();
    	try {
    		Socket.setSoTimeout(30000);
    		Socket_Out = new PrintWriter(Socket.getOutputStream());
    		Socket_In = new BufferedReader(new InputStreamReader(Socket.getInputStream(), Charset.forName("UTF-8")));
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
    J'ai fait quelques test en local en essayant de surcharger le serveur de client et de message à recevoir et à renvoyer.

    J'ai remarqué que lorsque je déconnecte un client, je me retrouve avec cette suite d'erreur et une partie des client sont déconnecté :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
     
    java.lang.IndexOutOfBoundsException: Index: 7, Size: 7
    	at java.util.ArrayList.RangeCheck(Unknown Source)
    	at java.util.ArrayList.get(Unknown Source)
    	at Serveur.Envoie(Serveur.java:228)
    	at Serveur.Quitter_Salon(Serveur.java:59)
    	at Joueur.Deconnexion(Joueur.java:271)
    	at Joueur.run(Joueur.java:68)
    java.lang.IndexOutOfBoundsException: Index: 5, Size: 4
    	at java.util.ArrayList.RangeCheck(Unknown Source)
    	at java.util.ArrayList.get(Unknown Source)
    	at Serveur.Envoie(Serveur.java:228)
    	at Serveur.Quitter_Salon(Serveur.java:69)
    	at Joueur.Deconnexion(Joueur.java:271)
    	at Joueur.run(Joueur.java:68)
    java.lang.IndexOutOfBoundsException: Index: 4, Size: 2
    	at java.util.ArrayList.RangeCheck(Unknown Source)
    	at java.util.ArrayList.get(Unknown Source)
    	at Serveur.Envoie(Serveur.java:228)
    	at Serveur.Quitter_Salon(Serveur.java:59)
    	at Joueur.Deconnexion(Joueur.java:271)
    	at Joueur.run(Joueur.java:68)
    java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
    	at java.util.ArrayList.RangeCheck(Unknown Source)
    	at java.util.ArrayList.get(Unknown Source)
    	at Serveur.Envoie(Serveur.java:228)
    	at Serveur.Quitter_Salon(Serveur.java:59)
    	at Joueur.Deconnexion(Joueur.java:271)
    	at Joueur.run(Joueur.java:68)
    java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
    	at java.util.ArrayList.RangeCheck(Unknown Source)
    	at java.util.ArrayList.get(Unknown Source)
    	at Serveur.Envoie(Serveur.java:228)
    	at Serveur.Quitter_Salon(Serveur.java:69)
    	at Joueur.Deconnexion(Joueur.java:271)
    	at Joueur.run(Joueur.java:68)
    Ya donc un problème de ce côté là :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    public void Envoie(ArrayList<Joueur> LS_JOUEUR, String MESSAGE) {
    	int NbJoueur = LS_JOUEUR.size();
    	for (int i = 0; i < NbJoueur; i++) {
    		LS_JOUEUR.get(i).Envoie(MESSAGE);
    	}
    }
    Si je remplace cette méthode par ça, ça ne bug plus :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    public void Envoie(ArrayList<Joueur> LS_JOUEUR, String MESSAGE) {
    	for (int i = 0; i < LS_JOUEUR.size(); i++) {
    		LS_JOUEUR.get(i).Envoie(MESSAGE);
    	}
    }
    Mais je pense pas que ça puisse régler tous les autres problèmes ^^

    Sinon pour eviter d'avoir à synchronizer ta liste LS_JOUEUR pourquoi ne pas avoir une queue de message par Joueur et lorsque le serveur veut envoyer un message à un client il le poste dans la queue de message?
    Je connais mal la programmation réseau, ça consiste en quoi exactement ? ^^

  7. #7
    Membre expert
    Avatar de natha
    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    2 346
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : Janvier 2006
    Messages : 2 346
    Points : 3 083
    Points
    3 083
    Par défaut
    Le problème c'est que si un joueur se déconnecte alors que tu es en parcours de la liste pour envoyer un message, tu vas avoir des incohérences dont l'exception que tu as donné.

    L'idéal serait de "copier" la liste de joueurs avant de la parcourir et de vérifier si le joueur est toujours connecté avant d'envoyer le message :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    public void Envoie(ArrayList<Joueur> LS_JOUEUR, String MESSAGE) {
            List<Joueur> joueurs = new ArrayList<Joueur>(LS_JOUEUR);
    	for (Joueur joueur : joueurs) {
                    if (joueur.isAlive()) { // Tester si le joueur est valide
    		       joueur.Envoie(MESSAGE);
                    }
    	}
    }
    Car ta modification actuelle par index posera des problèmes si par exemple le joueur 4 se déconnecte alors que tu es en plein parcours de ta liste => ton programme ne plantera pas mais un joueur risquerait de recevoir le MESSAGE à double ou au contraire de ne rien recevoir du tout.
    Comment ça ? La réponse à ton problème n'est ni dans la faq, ni dans les tutos, ni dans sources ??? Etonnant...
    De la bonne manière de poser une question (et de répondre).
    Je ne fais pas de service par MP. Merci (...de lire les règles...).
    Ma page dvp.com

  8. #8
    Membre du Club Avatar de Tigrounette
    Profil pro
    Inscrit en
    Décembre 2005
    Messages
    132
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2005
    Messages : 132
    Points : 69
    Points
    69
    Par défaut
    Merci, je vais essayer comme ça

  9. #9
    Membre habitué
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    132
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2007
    Messages : 132
    Points : 170
    Points
    170
    Par défaut
    Dans tous les cas il te manque une synchronization sur ton objet partagé.

    Dés que tu partages un objet entre plusieurs threads et que cet objet peut-être modifié alors tu dois le synchronisé. Sinon tu auras toujours des cas étranges ...

    Par exemple pour le parcours des joueurs
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public void Envoie(ArrayList<Joueur> LS_JOUEUR, String MESSAGE) {
            List<Joueur> joueurs = null;
     
            synchronized (LS_JOUEUR)){
            joueurs = new ArrayList<Joueur>(LS_JOUEUR);      
            }
     
    	for (Joueur joueur : joueurs) {
                    if (joueur.isAlive()) { // Tester si le joueur est valide
    		       joueur.Envoie(MESSAGE);
                    }
    	}
    }
    tu dois aussi synchronizer lorsque tu enlèves ou ajoutes un client exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    public void newPlayer (Jouer newPlayer){
       synchronized (LS_JOUEUR){ 
               LS_JOUEUR.add (newPlayer);
       }
    }
    Un autre moyen est d'utiliser une ConcurrentLinkedQueue

    Sinon le fait que tu gères l'envoie d'un message à tous les clients dans un seule boucle peut-être très pénalisant si certains de tes clients sont très lents et que tu tombes en timeout ou autre.

  10. #10
    Membre du Club Avatar de Tigrounette
    Profil pro
    Inscrit en
    Décembre 2005
    Messages
    132
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2005
    Messages : 132
    Points : 69
    Points
    69
    Par défaut
    Je viens de remplacer mon ArrayList par une ConcurrentLinkedQueue, et après quelques test, le serveur est vraiment beaucoup plus stable, merci du conseil

    Par contre, je comprend pas bien ta dernière phrase elmor, quand tu dis :

    Un autre moyen est d'utiliser une ConcurrentLinkedQueue

    Sinon le fait que tu gères l'envoie d'un message à tous les clients dans un seule boucle peut-être très pénalisant si certains de tes clients sont très lents et que tu tombes en timeout ou autre.
    Est-ce que le fait d'utiliser une ConcurrentLinkedQueue règle aussi le problème d'un client très lent ou d'un timeout ?

Discussions similaires

  1. Réponses: 1
    Dernier message: 10/12/2008, 19h22
  2. Comment utiliser un paquet dans une application ?
    Par zizo89 dans le forum Langage
    Réponses: 0
    Dernier message: 21/09/2007, 22h48
  3. Utilisation de GStreamer dans une application
    Par bouncebounce dans le forum C++
    Réponses: 4
    Dernier message: 13/04/2007, 16h44
  4. Utilisation de GStreamer dans une application
    Par bouncebounce dans le forum C
    Réponses: 1
    Dernier message: 13/04/2007, 08h24
  5. Utiliser un timer dans une application console
    Par chavers dans le forum Langage
    Réponses: 8
    Dernier message: 25/05/2005, 14h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo