04 noviembre 2005

Sincronización de hilos en java

No soy ningún experto en hilos, pero voy a contar aquí un poco de lo que sé. Quizás no sea lo más correcto, pero es la forma en la que yo los uso y me funcionan.

Para sincronizar hilos tenemos dos formas.

Sincronizar los métodos:

Por un lado podemos poner synchronize en los métodos de una clase. Sería algo como esto:

class Clase
{
public synchronized void metodo()
{
....
}
}

Si varios hilos llaman al método a la vez, sólo el primero consigue entrar. Los siguientes se quedan a la espera de que el primero termine.

Supongamos ahora que hay varios métodos synchronzied. Si un hilo entra en uno de ellos, todos los demás métodos synchronized quedan bloqueados para los demás hilos. No pueden llamar a ninguno de los demás métodos hasta que el primer hilo termine con el suyo.


Sincronizarse con un objeto:

Por otro lado podemos hacer un código sincronizado con un objeto. El código sería así

synchronized (cualquierObjeto)
{
// código que usa cualquierObjeto
}

Esto requiere la colaboración de todos los hilos. Cualquier hilo que quiera acceder a ese cualquierObjeto, debería hacerlo así. Mientras un hilo está en su trozo de código synchronized, ningún otro hilo puede acceder al cualquierObjeto.

¿Cuándo se usa uno y otro?

El primer sistema está bien para proteger atributos de la clase, de forma que ningún hilo los cambie mientras se está ejecutando uno de sus métodos. Sin embargo presenta una pequeña pega. Supongamos que tenemos una clase Lista con dos métodos sincronizados, un dameNumeroElementos() y un dameElemento(int i). Si un hilo quiere obtener los elementos, hará algo como esto

for (int i=0; i<lista.damenumeroelementos();>
{
dato = lista.dameElemento(i);
// tratamos el dato
}

Mientras se está haciendo el bucle, cualquier otro hilo puede incordiar. Mientras pedimos el número de elementos o pedimos un elemento, ningún otro hilo tiene acceso, pero mientras se está incrementando la i, se trata el dato o se compara si hemos llegado al final del bucle, cualquier hilo puede modificar la lista. En concreto puede pasar que el dameNumeroElementos() nos diga que hay un elemento, que otro hilo lo borre antes de que accedamos a él y que luego intentemos pedirlo, con el consiguiente error.

Cuando necesitamos hacer algo con un objeto llamando a varios métodos (como en el caso de obtener los elementos de la lista), es mejor usar esto

synchronized (lista)
{
for (i=0;i<lista.damenumeroelementos();i++
{
dato = lista.dameElemento(i);
// tratatmos el dato
}
}

Por supuesto, los el código de los demás hilos debe respetar este mecanismo, poniendo código sincronizado con lista cuando se quiera hacer algo con ella.

Este mecanismo también es bueno cuando el objeto en cuestión representa un recurso externo (por ejemplo, un InputStream o OutputStream de un fichero o un socket, o una Connection de una base de datos). Si nos sincronizamos con ese objeto, evitamos que varios hilos escriban o lean a la vez de ahí, impidiendo que los mensajes salgan o entren entremezclados.