Sincronizzazione Java 1.4: consentire l'esecuzione di una sola istanza del metodo (non bloccante)?

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

Domanda

Ho una classe che propone servizi di traduzione. Le traduzioni stesse devono essere ricaricate ogni 30 minuti. Per questo uso il supporto per Spring Timer. Fondamentalmente, la mia classe assomiglia a:

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

loadTranslations () può essere piuttosto lungo da eseguire, quindi mentre è in esecuzione le vecchie traduzioni sono ancora disponibili. Questo viene fatto caricando le traduzioni in una mappa locale e cambiando semplicemente il riferimento quando vengono caricate tutte le traduzioni.

Il mio problema è: come posso assicurarmi che quando un thread sta già caricando le traduzioni, è anche un secondo che prova a eseguirlo, lo rileva e ritorna immediatamente, senza avviare un secondo aggiornamento.

Un metodo sincronizzato metterà in coda solo i carichi ... Sono ancora su Java 1.4, quindi nessun java.util.concurrent.

Grazie per l'aiuto!

È stato utile?

Soluzione

Utilizzare una qualche forma di meccanismo di blocco per eseguire l'attività solo se non è già in corso. L'acquisizione del token di blocco deve essere un processo in una sola fase. Vedi:

/**
 * @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();

}

Codice di prova che genererà un'eccezione se l'attività viene eseguita contemporaneamente:

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();
        }
    }

}

Altri suggerimenti

Vengo da uno sfondo .net (nessuna esperienza java), ma potresti provare un semplice flag statico di qualche tipo che controlla all'inizio del metodo se il suo funzionamento è alrady. Quindi tutto ciò che devi fare è assicurarti che qualsiasi lettura / scrittura di quel flag sia sincronizzata. Quindi all'inizio controlla la bandiera, se non è impostata, impostala, se è impostata, ritorna. Se non è impostato, eseguire il resto del metodo e, al termine, disinserirlo. Assicurati solo di inserire il codice in un try / finally e il flag iunsetting in finally in modo che venga sempre disinserito in caso di errore. Molto semplificato ma potrebbe essere tutto ciò di cui hai bisogno.

Modifica: probabilmente funziona meglio della sincronizzazione del metodo. Perché hai davvero bisogno di una nuova traduzione immediatamente dopo quella prima che termini? E potresti non voler bloccare un thread troppo a lungo se deve aspettare un po '.

Mantenere un handle sul thread di caricamento per vedere se è in esecuzione?

O non puoi semplicemente usare un flag sincronizzato per indicare se è in corso un caricamento?

Questo è in realtà identico al codice richiesto per gestire la costruzione di un Singleton (sussulto!) quando fatto in modo classico:

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

Il test interno è identico al test esterno. Il test esterno è in modo che non entriamo abitualmente in un blocco sincronizzato, il test interno è per confermare che la situazione non è cambiata dall'ultima volta che abbiamo effettuato il test (il thread avrebbe potuto essere anticipato prima di entrare in Synchronized).

Nel tuo caso:

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

AGGIORNAMENTO: questo modo di costruire un singleton non funzionerà in modo affidabile con il tuo JDK1.4. Per la spiegazione vedi qui . Comunque penso che tu sia OK in questo scenario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top