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.
Excelente esplicación, felicitaciones
ResponderEliminar