Threads en Java (II)
30 de Octubre del 2016
Después de explicar unos conocimientos básicos en el primer post podemos entrar en materia con algo más de complejidad, para explicaros en qué consiste la sección crítica y la clausula synchronized que ponemos en los métodos utilizaré una actividad que realicé en clase que le viene como el guante.
Sección critica y synchronized
Entendemos sección crítica por la parte del código que es susceptible de generar errores de sincronización si dos o mas procesos (o hilos) están intentando acceder al mismo recurso.
Para evitar estos problemas de concurrencia debemos añadir en el método que es susceptible la clausula synchronized.
Los naufragos
Tenemos a unos pobres náufragos esperando ser rescatados por 3 balsas (hilos en ejecución) pero el hueco es muy estrecho y sólo una de ellas puede acceder (synchronized) a la vez a la zona donde están.
Con este enunciado estamos viendo como resolver el problema que surgiría si 3 hilos intentaran actualizar un número a la vez y los problemas de sincronía que tendrían.
Primero de todo tenemos a la clase Naufrago:
/*
* esta clase contiene el atributo que guarda el número actual de náufragos y
* el método para actualizarlo.
*/
public class Naufrago {
private int cantidadNaufragos = 0;
public int getCantidadNaufragos() {
return cantidadNaufragos;
}
public void setCantidadNaufragos(int cantidadNaufragos) {
this.cantidadNaufragos = cantidadNaufragos;
}
public Naufrago(){
}
public Naufrago(int cantidadNaufragos){
this.cantidadNaufragos = cantidadNaufragos;
}
// método para actualizar el número de náufragos
public void rescatesRestantes(int rescatados){
cantidadNaufragos -= rescatados;
}
}
Luego tenemos la clase Balsa que heredará de la clase Thread para que podamos definir un nuevo constructor añadiéndole un número que será la capacidad de las balsas. En nuestro caso cada balsa es un hilo en ejecución.
public class Balsa extends Thread {
private int numPlazas;
public Balsa() {
this.numPlazas = 0;
}
public Balsa(Runnable runnable, int numPlazas) {
super(runnable);
this.numPlazas = numPlazas;
}
public int getNumPlazas() {
return numPlazas;
}
public void setNumPlazas(int numPlazas) {
this.numPlazas = numPlazas;
}
}
Por último tenemos la clase donde ¡rescataremos a los náufragos! Esta clase la desglosaremos para un mejor entendimiento.
Primero de todo debemos instanciar una cantidad aleatoria (entre 800 y 1000) de náufragos a rescatar.
public class PruebaRescate implements Runnable {
private Naufrago nf = new Naufrago((int) (Math.random()*(1000-800)+800));
}
Después haremos una instancia Runnable (gracias a implementar Runnable) para asignársela a cada hilo (nuestra clase heredada de Thread: Balsa) junto a la capacidad de cada balsa. A continuación le daremos un nombre a cada hilo y los iniciaremos.
public static void main(String[] args) {
PruebaRescate pruebaRescateRunnable = new PruebaRescate();
Balsa balsa1 = new Balsa(pruebaRescateRunnable, (int) (Math.random() * (40 - 20) + 20));
Balsa balsa2 = new Balsa(pruebaRescateRunnable, (int) (Math.random() * (50 - 30) + 30));
Balsa balsa3 = new Balsa(pruebaRescateRunnable, (int) (Math.random() * (60 - 40) + 40));
balsa1.setName("Balsa 1");
balsa2.setName("Balsa 2");
balsa3.setName("Balsa 3");
balsa1.start();
balsa2.start();
balsa3.start();
}
Por último tenemos los dos métodos que realizaran el rescate.
/*
* este método sólo se ejecuta mientras queden náufragos, llama al siguiente método
* y además almacena la cantidad de plazas para cada balsa.
*/
@Override
public void run() {
while(nf.getCantidadNaufragos() >= 0) {
// guardamos el número de plazas para cada balsa (hilo)
int numeroPlazas = ((Balsa) Thread.currentThread()).getNumPlazas();
// llamamos al método sincronizado para rescatar náufragos
rescatar(numeroPlazas);
}
}
/*
* método sincronizado para evitar problemas de concurrencia si las 3 balsas
* intentan rescatar a la vez a los náufragos, con esto sólo permitimos el pasado
* a una de ellas para que el número de náufragos restante se actualice correctamente.
*/
private synchronized void rescatar(int cantidadPlazas) {
if(nf.getCantidadNaufragos()>=0){
// mostramos el número de náufragos restantes
System.out.println("\nQuedan " + nf.getCantidadNaufragos() + " náufragos");
// mostramos la balsa actual y la cantidad de plazas de ésta
System.out.println("La balsa actual es " + Thread.currentThread().getName()
+ " y tiene capacidad para " + cantidadPlazas);
// asignamos un tiempo entre 1 y 3 segundos que tarda cada balsa en
// recoger a los náufragos y llegar la siguiente
try{
Thread.sleep((int) (Math.random()*(3-1)+1*(1000)));
}catch(Exception e) {
e.printStackTrace();
}
// actualizamos el número de náufragos
nf.rescatesRestantes(cantidadPlazas);
} else {
// cuando una balsa está rescatando a los últimos náufragos las demás
// vuelven a su puesto
System.out.println("\nYa no quedan náufragos y la " + Thread.currentThread().getName()
+ " está volviendo a su puesto");
}
}
Si ejecutáramos este pequeño programa (he reducido el número de náufragos para que el post no sea excesivamente largo) obtendríamos un resultado como el de abajo en el que vemos como sólo una balsa puede acceder a los náufragos en cada momento, por lo que el número restante de éstos se actualiza correctamente.
Quedan 144 náufragos
La balsa actual es Balsa 3 y tiene capacidad para 59
Quedan 85 náufragos
La balsa actual es Balsa 1 y tiene capacidad para 24
Quedan 61 náufragos
La balsa actual es Balsa 1 y tiene capacidad para 24
Quedan 37 náufragos
La balsa actual es Balsa 1 y tiene capacidad para 24
Quedan 13 náufragos
La balsa actual es Balsa 1 y tiene capacidad para 24
Ya no quedan náufragos y la Balsa 3 está volviendo a su puesto
Ya no quedan náufragos y la Balsa 2 está volviendo a su puesto
Terminamos este doble post sobre los hilos y como gestionarlos en Java, así que como viene siendo ya habitual os recuerdo mi correo iam@jmoral.es y mi Twitter @owniz para que puedas preguntarme cualquier duda.
Un saludo. ツ