Synchronisation Java 1.4: autoriser l’exécution d’une seule instance de la méthode (non bloquante)?

StackOverflow https://stackoverflow.com/questions/152138

Question

J'ai une classe proposant des utilitaires de traduction. Les traductions elles-mêmes doivent être rechargées toutes les 30 minutes. J'utilise le support Spring Timer pour cela. En gros, ma classe ressemble à:

public interface Translator {
    public void loadTranslations();
    public String getTranslation(String key);
}

loadTranslations () peut être assez long à exécuter. Par conséquent, les anciennes traductions sont toujours disponibles. Cela se fait en chargeant les traductions dans une carte locale et en modifiant simplement la référence lorsque toutes les traductions sont chargées.

Mon problème est le suivant: comment puis-je m'assurer que lorsqu'un thread charge déjà des traductions, un deuxième essaye également de s'exécuter, il le détecte et revient immédiatement, sans démarrer une deuxième mise à jour.

Une méthode synchronisée ne mettra que les charges en file d'attente ... Je suis toujours sur Java 1.4, donc pas de java.util.concurrent.

Merci pour votre aide!

Était-ce utile?

La solution

Utilisez un mécanisme de verrouillage pour exécuter la tâche uniquement si elle n’est pas déjà en cours. L’acquisition du jeton de verrouillage doit être un processus en une étape. Voir:

/**
 * @author McDowell
 */
public abstract class NonconcurrentTask implements Runnable {

    private boolean token = true;

    private synchronized boolean acquire() {
        boolean ret = token;
        token = false;
        return ret;
    }

    private synchronized void release() {
        token = true;
    }

    public final void run() {
        if (acquire()) {
            try {
                doTask();
            } finally {
                release();
            }
        }
    }

    protected abstract void doTask();

}

Code de test qui lève une exception si la tâche est exécutée simultanément:

public class Test {

    public static void main(String[] args) {
        final NonconcurrentTask shared = new NonconcurrentTask() {
            private boolean working = false;

            protected void doTask() {
                System.out.println("Working: "
                        + Thread.currentThread().getName());
                if (working) {
                    throw new IllegalStateException();
                }
                working = true;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (!working) {
                    throw new IllegalStateException();
                }
                working = false;
            }
        };

        Runnable taskWrapper = new Runnable() {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    shared.run();
                }
            }
        };
        for (int i = 0; i < 100; i++) {
            new Thread(taskWrapper).start();
        }
    }

}

Autres conseils

Je viens d’un fond .net (pas d’expérience Java), mais vous pouvez essayer un simple drapeau statique qui vérifie au début de la méthode si elle est en cours d’exécution. Ensuite, tout ce que vous avez à faire est de vous assurer que toute lecture / écriture de cet indicateur est synchronisée. Donc, au début, vérifiez le drapeau, s'il n'est pas défini, définissez-le, s'il est défini, retournez. S'il n'est pas défini, exécutez le reste de la méthode et, une fois terminée, désactivez-la. Assurez-vous simplement de mettre le code dans un essai / enfin et que le drapeau est inséré dans le dernier afin qu'il soit toujours désactivé en cas d'erreur. Très simplifié mais peut-être tout ce dont vous avez besoin.

Edit: Cela fonctionne probablement mieux que la synchronisation de la méthode. Parce que vous avez vraiment besoin d’une nouvelle traduction immédiatement après celle qui précède? Et vous ne voudrez peut-être pas verrouiller un fil trop longtemps s'il doit attendre un moment.

Gardez une poignée sur le fil de charge pour voir s’il fonctionne?

Ou pouvez-vous simplement utiliser un indicateur synchronisé pour indiquer si un chargement est en cours?

Ceci est en fait identique au code requis pour gérer la construction d'un Singleton (gasp!) lorsque vous avez terminé de manière classique:

if (instance == null) {
  synchronized {
    if (instance == null) {
       instance = new SomeClass();
    }
  }
}

Le test interne est identique au test externe. Le test externe permet d'éviter d'entrer systématiquement dans un bloc synchronisé. Le test interne consiste à confirmer que la situation n'a pas changé depuis le dernier test (le thread aurait pu être préempté avant d'entrer dans Synchronized).

Dans votre cas:

if (translationsNeedLoading()) {
  synchronized {
    if (translationsNeedLoading()) {
       loadTranslations();
    }
  }
}

UPDATE: Cette façon de construire un singleton ne fonctionnera pas de manière fiable avec votre JDK1.4. Pour en savoir plus, voir ici . Cependant, je pense que vous êtes d'accord pour ce scénario.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top