La comprensión de por qué es inseguro para iniciar un hilo dentro de un constructor en términos del modelo de memoria de Java

Kenny Wong:

De acuerdo con Java concurrencia en la práctica , es peligroso para iniciar un hilo dentro de un constructor de la clase. La razón es que esto expone el thispuntero a otro hilo antes de que el objeto está totalmente construido.

A pesar de este tema se discute en muchas preguntas StackOverflow anteriores, todavía estoy teniendo dificultades para entender por qué esto es como una preocupación. En particular, espero que buscar claridad sobre si iniciar un subproceso dentro de un constructor puede conducir a problemas de consistencia de memoria desde el punto de vista del modelo de memoria de Java.

Déjeme darle un ejemplo concreto de la clase de cosas que quiero hacer. (La salida deseado para este trozo de código es para el número 20 a imprimir a la consola.)

private static class ValueHolder {

    private int value;
    private Thread thread;

    ValueHolder() {
        this.value = 10;
        thread = new Thread(new DoublingTask(this)); // exposing "this" pointer!!!
        thread.start();   // starting thread inside constructor!!!
    }

    int getValue() {
        return value;
    }

    void awaitTermination() {
        try {
            thread.join();
        } catch (InterruptedException ex) {}
    }

}

private static class DoublingTask implements Runnable {

    private ValueHolder valueHolder;

    DoublingTask(ValueHolder valueHolder) {
        this.valueHolder = valueHolder;
    }

    public void run() {
        System.out.println(Thread.currentThread().getName());
        System.out.println(valueHolder.getValue() * 2);  // I expect to print out 20...
    }

}

public static void main(String[] args) {
    ValueHolder myValueHolder = new ValueHolder();
    myValueHolder.awaitTermination();
}

Sí, ya sé que el hilo se inicia antes de regresar desde el constructor. Sí, ya sé que el thispuntero se expone al hilo. Y, sin embargo, estoy convencido de que el código es correcto. Creo que siempre va a imprimir el número 20 a la consola.

  • La asignación this.value = 10 sucede-antes thread.start() . (Esto se debe a que this.value = 10precede thread.start()en el orden del programa.)
  • El llamado de thread.start()en el hilo principal que ocurre antes del inicio del .run()método en el hilo de nueva creación. (Debido a que thread.start()es una acción de sincronización.)
  • El inicio del .run()método ocurre antes de la System.out.println(valueHolder.getValue() * 2);sentencia de impresión. (De nuevo, por orden del programa.)

Por lo tanto, por el modelo de memoria de Java, la declaración de impresión debe leer el valor correctamente inicializado-de valueHolder.value(a saber, 10). Así que a pesar de haber ignorado el consejo de Java concurrencia en la práctica , todavía me parece que he escrito una pieza correcta de código.

¿He cometido un error? ¿Qué me estoy perdiendo?


ACTUALIZACIÓN : En base a las respuestas y comentarios, ahora entiendo que mi ejemplo de código es funcionalmente correcto por las razones que he proporcionado. Sin embargo, es mala práctica de escribir código de esta manera porque existe la posibilidad de que otros desarrolladores en el futuro agregar instrucciones de inicialización adicionales después de la partida de la rosca. Una situación en la que tales errores son probablemente es en la aplicación de subclases de esta clase.

Miguel :

Supongamos que subclase de la clase. Puede que no haya inicializado sus campos en el momento en que se necesiten.

class BetterValueHolder extends ValueHolder
{
    private int betterValue;

    BetterValueHolder(final int betterValue)
    {
        // not actually required, it's added implicitly anyway.
        // just to demonstrate where your constructor is called from
        super();

        try
        {
            Thread.sleep(1000); // just to demonstrate the race condition more clearly
        }
        catch (InterruptedException e) {}

        this.betterValue = betterValue;
    }

    @Override
    public int getValue()
    {
        return betterValue;
    }
}

Esto imprimirá cero, independientemente de qué valor se le da al constructor BetterValueHolder.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=172969&siteId=1
Recomendado
Clasificación