3 votes

HashMap et ArrayList ajoutant tout en itérant/bouclant

J'ai un jeu où toutes les X secondes, il écrira les valeurs modifiées en mémoire dans ma base de données. Ces valeurs sont stockées dans des conteneurs (HashMaps et ArrayLists) lorsque les données qu'ils contiennent sont modifiées.

Pour simplifier, faisons comme si je n'avais qu'un seul conteneur à écrire dans la base de données:

public static HashMap dbEntitiesDeletesBacklog = new HashMap();

Ma boucle d'écriture dans la base de données:

Timer dbUpdateJob = new Timer();
dbUpdateJob.schedule(new TimerTask() {
    public void run() {
        long startTime = System.nanoTime();
        boolean updateEntitiesTableSuccess = UpdateEntitiesTable();
        if (!updateEntitiesTableSuccess){
            try {
                conn.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
                logger.fatal(e.getMessage());
                System.exit(1);
            }
        } else { //everything saved to DB - commit time
            try {
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
                logger.fatal(e.getMessage());
                System.exit(1);
            }
        }
        logger.debug("Temps d'enregistrement dans la base de données : " + (System.nanoTime() - startTime) / 1000000 + " millisecondes");
    }
}, 0, 10000); //TODO:: trouver le délai d'enregistrement parfait

Ma méthode de mise à jour:

private boolean UpdateEntitiesTable() {
    Iterator> it = dbEntitiesDeletesBacklog.entrySet().iterator();
    while (it.hasNext()) {
        Entry pairs = it.next();
        String tmpEntityId = pairs.getKey();

        int deletedSuccess = UPDATE("DELETE" + 
                " FROM " + DB_NAME + ".entities" + 
                " WHERE entity_id=(?)", new String[]{tmpEntityId});
        if (deletedSuccess != 1) {
            logger.error("L'entité " + tmpEntityId + " n'a pas pu être supprimée.");
            return false;
        }
        it.remove();
        dbEntitiesDeletesBacklog.remove(tmpEntityId);
    }

Dois-je créer une sorte de mécanisme de verrouillage pendant la 'sauvegarde dans la base de données' pour le HashMap dbEntitiesDeletesBacklog et d'autres conteneurs non inclus dans cet extrait ? Je pense que oui, car il crée son itérateur, puis boucle. Que se passe-t-il si quelque chose est ajouté après la création de l'itérateur et avant la fin de la boucle à travers les entrées. Désolé, c'est plus une question de processus que d'aide au code (puisque j'ai inclus autant de code d'exemple), mais je voulais m'assurer que c'était facile à comprendre ce que j'essaie de faire et de demander.

Même question pour mes autres conteneurs que j'utilise comme ceci:

public static ArrayList dbCharacterDeletesBacklog =  new ArrayList();

private boolean DeleteCharactersFromDB() {
    for (String deleteWho : dbCharacterDeletesBacklog){
        int deleteSuccess = MyDBSyncher.UPDATE("DELETE FROM " + DB_NAME + ".characters" +
                " WHERE name=(?)", 
                new String[]{deleteWho});

        if (deleteSuccess != 1) {
            logger.error("Personnage (deleteSuccess) : " + deleteSuccess);
            return false;
        }
    }
    dbCharacterDeletesBacklog.clear();
    return true;
}

Merci beaucoup, comme toujours, pour toute aide sur ce sujet. C'est grandement apprécié !!

2voto

increment1 Points 3144

Au minimum, vous avez besoin d'une carte synchronisée (via Collections.synchronizedMap) si vous accédez à votre carte de manière concurrentielle, sinon vous pourriez rencontrer un comportement non déterministe.

Au-delà de cela, comme vous le suggérez, vous devez également verrouiller votre carte lors de l'itération. Dans la javadoc pour Collections.synchronizedMap(), la suggestion est la suivante :

Il est impératif que l'utilisateur synchronise manuellement sur la carte retournée lors de l'itération sur l'une de ses vues de collection :

Map m = Collections.synchronizedMap(new HashMap());
      ...
Set s = m.keySet();  // Ne doit pas être dans un bloc synchronisé
      ...
synchronized(m) {  // Synchronisation sur m, pas sur s !
    Iterator i = s.iterator(); // Doit être dans un bloc synchronisé
    while (i.hasNext())
        foo(i.next());
}

Le non-respect de ce conseil peut entraîner un comportement non déterministe.

Alternativement, utilisez un ConcurrentHashMap au lieu d'une HashMap régulière pour éviter de nécessiter une synchronisation pendant l'itération. Pour un jeu, il s'agit probablement d'une meilleure option car vous évitez de verrouiller votre collection pendant une longue période.

Peut-être même mieux, envisagez de faire tourner de nouvelles collections de sorte que chaque fois que vous mettez à jour la base de données, vous récupérez la collection et la remplacez par une nouvelle vide où toutes les nouvelles mises à jour sont écrites, évitant ainsi de bloquer la collection pendant l'écriture des données de la base. Les collections dans ce cas seraient gérées par un conteneur pour permettre cette prise et ce remplacement en toute sécurité. <<< Remarque : Vous ne pouvez pas exposer la collection sous-jacente dans ce cas au code de modification car vous devez garder sa référence strictement privée pour que le remplacement soit efficace (et ne pas introduire de conditions de concurrence).

0voto

JayAvon Points 311

Voici un exemple de ce que j'utiliserai. Je le poste ici dans l'espoir que cela puisse aider quelqu'un d'autre qui rencontre un problème similaire.

public class MyDBSyncher {

    public static boolean running = false;
    public static HashMap dbEntitiesInsertsBacklog_A = new HashMap();
    public static HashMap dbEntitiesInsertsBacklog_B = new HashMap();

    public MyDBSyncher(){
        Timer dbUpdateJob = new Timer();
        dbUpdateJob.schedule(new TimerTask() {
            public void run() {
                running = true;
                boolean updateEntitiesTableSuccess = UpdateEntitiesTable();
                running = false;
            }
        }, 0, 10000); //TODO:: trouver le délai de sauvegarde parfait
    }

    public HashMap getInsertableEntitiesHashMap(){
        if (running){
            return dbEntitiesInsertsBacklog_B;
        } else {
            return dbEntitiesInsertsBacklog_A;
        }
    }

    private boolean UpdateEntitiesTable() {
        Iterator> it2 = getInsertableEntitiesHashMap().entrySet().iterator();
        while (it2.hasNext()) {
            Entry pairs = it2.next();
            String tmpEntityId = pairs.getKey();

            //quelques mises à jour de la base de données ici

            it2.remove();
            getInsertableEntitiesHashMap().remove(tmpEntityId);
        }
        return true;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X