viernes, 19 de octubre de 2012

Sincronización (Threads)


La importancia de la sincronización
La programación concurrente puede dar lugar a muchos errores debido a la utilización de recursos compartidos que pueden ser alterados. Las secciones de código potencialmente peligrosas de provocar estos errores se conocen como secciones críticas.

En general los programas concurrentes deben ser correctos totalmente. Este concepto es la suma de dos conceptos distintos de la corrección parcial(o safety) esto es que terminara en algún momento de tiempo finito. En esto programas por lo tanto hay que evitar que varios hilos entren en una sección critica (exclusión mutua o mutex) y que los programas se bloqueen (deadlock).


Además los programas que no terminan nunca (programas reactivos) deben cumplir la ausencia de inanición (starvation) esto es que tarde o temprano todos los hilos alcancen el control del procesador y ninguno quede indefinidamente en la lista de hilos listos y también deben cumplir la propiedad de equitatividad (fairwess) esto es repartir el tiempo de la forma mas justa entre todos los hilos.

Un monitor impide que varios hilos accedan al mismo recurso compartido a la vez. Cada monitor incluye un protocolo de entrada y un protocolo de salida. En Java los monitores se consiguen mediante el uso de la palabra reservada syncronized bien como instrucción de bloque o bien como modificador de acceso de métodos.

La keyword synchronized – Exclusion Mutua –
Cuando se utiliza la palabra clave synchronized se esta indicando una zona restringida para el uso de Threads, esta zona restringida para efectos prácticos puede ser considerada un candado (“lock”) sobre la instancia del objeto en cuestión.

Lo anterior implica que si es invocado un método synchronized únicamente el Thread que lo invoca tiene acceso a la instancia del objeto, y cualquier otro Thread que intente acceder a esta misma instancia tendrá que esperar hasta que sea terminada la ejecución del método synchronized.

Resumiendo y agregando algunas cosas mas podemos decir:

•Es posible garantizar la ejecución en exclusión mutua de un método definiéndolo como  synchronized.
• Los métodos synchronized bloquean el cerrojo del objeto actual, o del objeto Class si el método es estático.
• Si el cerrojo está ocupado, el hilo se suspende hasta que éste es liberado.
• No se ejecutarán simultáneamente dos métodos synchronized de un mismo objeto, pero sí uno que lo sea y cualquier número de otros que no.
•Pueden sobrescribirse métodos synchronized para que no lo sean en las clases nuevas. Sin embargo sí lo seguirá siendo el método super.metodo(...).
• La exclusión mutua es interesante para garantizar la consistencia del estado de los objetos.
• Se suelen utilizar en los métodos que hacen que el objeto pase por estados transitorios que no son correctos. Si también se hacen synchronized los métodos para consultar el estado, se evita que puedan verse estados inestables del objeto.
•Puede bloquearse el cerrojo de un objeto dentro de una porción de código mediante
synchronized(objeto) { }.
• Si el cerrojo está bloqueado por un hilo diferente (ya sea porque está ejecutando un método synchronized o porque está dentro de un bloque como el anterior), el hilo actual se suspenderá.
• Pueden usarse estos bloques para clases sin sincronizar que no podamos modificar. Es inseguro, mejor usar herencia.

Candados (locks)
Todos los objetos (incluidos los arrays) tienen un “candado” (lock).  Solo un hilo puede tener bloqueado el candado de un objeto en un momento dado. Podrá bloquearlo más de una vez antes de liberarlo y solo quedará completamente libre cuando el hilo lo libere tantas veces como lo ha obtenido.  Si  un hilo intenta obtener un candado ocupado, quedará suspendida hasta que éste se libere y pueda obtenerlo. No se puede acceder directamente a los candados.

Los métodos wait() y notify() - notifyAll()
Los mecanismos anteriores sirven para evitar la interferencia entre hilos. Es necesario algún método de comunicación entre ellos. Todos los objetos implementan los métodos wait () y notify ().  Mediante wait () se suspende el hilo actual hasta que algún otro llame al método notify () del mismo objeto. Todos los objetos tienen una lista de hilos que están esperando que alguien llame a notify ().

Estos métodos están pensados para avisar de cambios en el estado del objeto a hilos que están esperando dichos cambios:

synchronized void hacerMientrasCondicion() {
while(!condicion) wait();
/* ... */
}
synchronized void cambiarCondicion() {
/*...*/
notify();
}

Tanto el método wait () como el notify () deben ejecutarse dentro de métodos synchronized.
Cuando se llama a wait () se libera el cerrojo del objeto que se tiene bloqueado y se suspende el hilo, de forma atómica. Cuando se llama a notify () se despierta una de los hilos que estaban esperando. Ésta competirá con cualquier otra por volver a obtener el candado del objeto, sin tener ningún tipo de prioridad. De ahí es que sea mejor usar while(!condicion) wait();

Aclaración:

•Todos los métodos anteriores son finales.
•Todas las variantes de wait (...) pueden lanzar  la excepción InterruptedException.
•notify () despierta un hilo cualquiera. No se garantiza que sea el que más tiempo lleva esperando. Es solo interesante cuando se está seguro de que solo habrá un hilo esperando. Es peligroso.
•notifyAll () despierta a todos los hilos. Es más seguro. Requiere más que nunca el uso de while() en lugar de if(!condition) wait();

Interbloqueos
La existencia de varios hilos, y el uso de la exclusión mutua pueden ocasionar la aparición de interbloqueos, en el que ninguno de dos hilos puede ejecutarse porque están esperando algo de la otra.

Java no controla el interbloqueo. No se preocupa de detectarlo. Es responsabilidad del diseñador de la aplicación ser cuidadoso para evitar la aparición de interbloqueos.

1 comentario: