Sincronización de Java 1.4: ¿solo se permite ejecutar una instancia de método (sin bloqueo)?

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

Pregunta

Tengo una clase que propone servicios de traducción. Las traducciones deben ser recargadas cada 30 minutos. Uso el soporte Spring Timer para eso. Básicamente, mi clase se ve como:

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

loadTranslations () puede ser bastante largo de ejecutar, así que mientras se está ejecutando, las traducciones antiguas todavía están disponibles. Esto se hace cargando las traducciones en un Mapa local y simplemente cambiando la referencia cuando se cargan todas las traducciones.

Mi problema es: ¿cómo me aseguro de que cuando un subproceso ya está cargando las traducciones, un segundo también intente ejecutarse, lo detecte y regrese inmediatamente, sin iniciar una segunda actualización?

Un método sincronizado solo pondrá en cola las cargas ... Todavía estoy en Java 1.4, así que no hay java.util.concurrent.

¡Gracias por tu ayuda!

¿Fue útil?

Solución

Use algún tipo de mecanismo de bloqueo para realizar la tarea solo si aún no está en progreso. La adquisición del token de bloqueo debe ser un proceso de un solo paso. Ver:

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

}

Código de prueba que generará una excepción si la tarea se ejecuta simultáneamente:

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

}

Otros consejos

Soy de un fondo .net (no hay ninguna experiencia en Java), pero puedes probar un simple indicador estático de algún tipo que verifique al principio del método si está funcionando. Luego, todo lo que necesita hacer es asegurarse de que cualquier lectura / escritura de ese indicador esté sincronizada. Por lo tanto, al principio, compruebe el indicador, si no está establecido, configúrelo, si está establecido, devuelva. Si no está configurado, ejecute el resto del método y, una vez completado, anule la configuración. Solo asegúrese de poner el código en un intento / finalmente y la bandera que se encuentra en el final para que siempre se desactive en caso de error. Muy simplificado, pero puede ser todo lo que necesitas.

Editar: Esto en realidad probablemente funciona mejor que sincronizar el método. ¿Porque realmente necesita una nueva traducción inmediatamente después de la anterior? Y es posible que no desee bloquear un hilo por mucho tiempo si tiene que esperar un poco.

¿Mantener un identificador en el subproceso de carga para ver si se está ejecutando?

¿O no puedes usar un indicador sincronizado para indicar si una carga está en curso?

Esto es en realidad idéntico al código que se requiere para administrar la construcción de un Singleton (¡jadeo!) cuando se realiza de forma clásica:

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

La prueba interna es idéntica a la prueba externa. La prueba externa es para que no ingresemos rutinariamente en un bloque sincronizado, la prueba interna es para confirmar que la situación no ha cambiado desde la última vez que hicimos la prueba (el hilo podría haberse adelantado antes de entrar en Sincronizado).

En tu caso:

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

ACTUALIZACIÓN: esta forma de construir un singleton no funcionará de manera confiable bajo su JDK1.4. Para la explicación vea aquí . Sin embargo, creo que estarás bien en este escenario.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top