threads termine sincronizar sincronizados que otro hacer espere esperar despertar como java multithreading synchronization synchronized final

termine - synchronized java netbeans



¿Cómo evitar la sincronización en un campo no final? (3)

Algunas plataformas proporcionan primitivas explícitas de barrera de memoria que garantizarán que si un hilo escribe en un campo y luego crea una barrera de escritura, cualquier hilo que nunca haya examinado el objeto en cuestión pueda garantizar el efecto de esa escritura. Desafortunadamente, desde la última vez que formulé una pregunta, la forma más económica de establecerse ocurre antes que con el campo no final , el único momento en que Java podía ofrecer garantías de semántica de subprocesamiento sin requerir ninguna acción especial en nombre de un hilo de lectura. usando campos final . Java garantiza que cualquier referencia hecha a un objeto a través de un campo final verá cualquier almacenamiento que se realizó en campos finales o no de ese objeto antes de que la referencia se almacenara en el campo final, pero esa relación no es transitiva. Por lo tanto, dado

class c1 { public final c2 f; public c1(c2 ff) { f=ff; } } class c2 { public int[] arr; } class c3 { public static c1 r; public static c2 f; }

Si lo único que escribe en c3 es un hilo que realiza el código:

c2 cc = new c2(); cc.arr = new int[1]; cc.arr[0] = 1234; c3.r = new c1(cc); c3.f = c3.r.f;

un segundo hilo realiza:

int i1=-1; if (c3.r != null) i1=c3.r.f.arr[0];

y un tercer hilo realiza:

int i2=-1; if (c3.f != null) i2=c3.f.arr[0];

El estándar Java garantiza que el segundo subproceso, si la condición if es true , establece i1 a 1234. El tercer subproceso, sin embargo, posiblemente vea un valor no nulo para c3.f y, sin embargo, vea un valor null para c3.arr o vea cero en c3.f.arr[0] . Aunque el valor almacenado en c3.f haya sido leído desde c3.rf y todo lo que lea la referencia final c3.rf sea ​​necesario para ver cualquier cambio realizado en ese objeto identificado antes de la referencia c3.rf , nada en el Java Standard prohibiría al JIT reordenar el código del primer hilo como:

c2 cc = new c2(); c3.f = cc; cc.arr = new int[1]; cc.arr[0] = 1234; c3.r = new c1(cc);

Tal reescritura no afectaría al segundo hilo, pero podría causar estragos en el tercero.

Si tenemos 2 clases que operan en el mismo objeto bajo diferentes hilos y queremos evitar las condiciones de carrera, tendremos que usar bloques sincronizados con el mismo monitor como en el siguiente ejemplo:

class A { private DataObject mData; // will be used as monitor // thread 3 public setObject(DataObject object) { mData = object; } // thread 1 void operateOnData() { synchronized(mData) { mData.doSomething(); ..... mData.doSomethingElse(); } } } class B { private DataObject mData; // will be used as monitor // thread 3 public setObject(DataObject object) { mData = object; } // thread 2 void processData() { synchronized(mData) { mData.foo(); .... mData.bar(); } } }

El objeto en el que setObject() se establecerá llamando a setObject() y no cambiará después. Usaremos el objeto como un monitor. Sin embargo, intelliJ advertirá sobre la sincronización en un campo no final.

En este escenario particular, ¿el campo no local es una solución aceptable?

Otro problema con el enfoque anterior es que no se garantiza que el monitor (mData) será observado por el hilo 1 o el hilo 2 después de haber sido configurado por el hilo 3, porque no se ha establecido una relación "pasar antes" entre ajuste y leyendo el monitor. Todavía podría observarse como null por el hilo 1, por ejemplo. ¿Mi especulación es correcta?

Con respecto a las posibles soluciones, hacer que DataObject sea ​​seguro para subprocesos no es una opción. Configurar el monitor en el constructor de las clases y declararlo final puede funcionar.

EDITAR Semánticamente, la exclusión mutua necesaria está relacionada con DataObject . Esta es la razón por la que no quiero tener un monitor secundario. Una solución sería agregar los métodos lock() y unlock() en DataObject que necesitan ser llamados antes de trabajar en él. Internamente usarían un objeto de Lock . Entonces, el método operateOnData() se convierte en:

void operateOnData() { mData.lock() mData.doSomething(); ..... mData.doSomethingElse(); mData.unlock(); }


Puedes crear un contenedor

class Wrapper { DataObject mData; synchronized public setObject(DataObject mData) { if(this.mData!=null) throw ..."already set" this.mData = mData; } synchronized public void doSomething() { if(mData==null) throw ..."not set" mData.doSomething(); }

Se crea un objeto contenedor y se pasa a A y B

class A { private Wrapper wrapper; // set by constructor // thread 1 operateOnData() { wrapper.doSomething(); }

El hilo 3 también tiene una referencia al envoltorio; llama a setObject() cuando está disponible.


Una solución simple es simplemente definir un objeto final público estático para usar como el bloqueo. Declararlo así:

/**Used to sync access to the {@link #mData} field*/ public static final Object mDataLock = new Object();

Luego, en el programa, sincronice en mDataLock en lugar de mData.

Esto es muy útil, porque en el futuro alguien puede cambiar mData de modo que su valor cambie, entonces su código tendrá una serie de errores de enhebrado raros.

Este método de sincronización elimina esa posibilidad. También es realmente bajo costo.

Además, tener el bloqueo sea estático significa que todas las instancias de la clase comparten un solo bloqueo. En este caso, eso parece lo que quieres.

Tenga en cuenta que si tiene muchas instancias de estas clases, esto podría convertirse en un cuello de botella. Como todas las instancias ahora comparten un bloqueo, solo una instancia puede cambiar cualquier mData a la vez. Todas las demás instancias tienen que esperar.

En general, creo que algo así como un contenedor para los datos que desea sincronizar es un mejor enfoque, pero creo que esto funcionará.

Esto es especialmente cierto si tiene múltiples instancias simultáneas de estas clases.