Curso de Java | Hilos

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.

Para usarlos puedo extender thread o implementar Runnable

Dos ejemplos de hilos en la vida real

Cuando se venden las entradas del concierto de Madonna. En poco segundos todas las entradas quedan agotadas debido a la gran demanda. El proceso que materializa la compra de la entrada no debe ser realizado por varios usuarios a la vez, ya que se corre el riesgo de que se vendan más entradas de las que realmente hay disponibles. Esa parte del código habrá que sincronizarla.

Varias personas entran en un supermercado en el que sólo hay un dependiente. Aunque cada cliente deambula por el supermercado a su aire, mirando productos, escogiendo los que tienen menos conservantes, etc. a la hora de pagar los clientes deben ponerse en cola e ir siendo atendidos por el vendedor de uno a uno. Esta sería la parte sincronizada de un algoritmo que representase lo que ocurre en el supermercado.

Ejemplo de aplicación sin usar hilos

Aunque creamos 3 «pseudohilos», realmente no lo son, y el código de un hilo no se ejecutará hasta que no haya concluído el «pseudohilo» anterior.

Main.java
public class Main {
	public static void main(String args[]){
		HiloFalso hilo1 = new HiloFalso();
		HiloFalso hilo2 = new HiloFalso();
		HiloFalso hilo3 = new HiloFalso();
		hilo1.run();
		hilo2.run();
		hilo3.run();	
	}
}

Output:
Variable1:90.0
Variable1:80.0
Variable1:70.0

Hilo.java
class HiloFalso{
	static double variable1 = 100;
	public void run() {
		variable1-=10;	
		Thread.sleep(500);		
		System.out.println("Variable1:" + variable1);
	}
}

Extender Thread

Cuando en una instancia que herede de thread llamo al método start estoy invocando el método run de la clase en un nuevo hilo.

Si llamase el método run directamente, estaría ejecutando su código pero sin crear un nuevo hilo.

Método Main para las siguientes implementaciones de hilos:
public static void main(String args[]){
	Hilo hilo1 = new Hilo();
	Hilo hilo2 = new Hilo();
	Hilo hilo3 = new Hilo();
		
	hilo1.start();
	hilo2.start();
	hilo3.start();
		
}

Puedo asignar prioridad a los hilos usando:
Hilo1.setPriority((Thread.MAX_PRIORITY – Thread.NORM_PRIORITY) / 2);

Sin sincronización

class Hilo extends Thread{
	static double variable1 = 100;
	public void run() {
		try {
			variable1-=10;
			Thread.sleep(500);
			System.out.println("Variable1:" + variable1 );
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

Ponemos los hilos a dormir para asegurarnos de que en algún momento todos los hilos están ejecutando simultáneamente el método run().

Output:
Variable1:70.0
Variable1:70.0
Variable1:70.0

Modificador synchronized

Sólo permite que un hilo pase por cierto código de cada vez.

Cuando un objeto está sincronizado, se dice que dicho objeto es un monitor y tiene una variable lock.

El lock tiene el id de un hilo.

Con sincronización

class Hilo extends Thread{
	static final Object mutex = new Object();
	static double variable = 100;
	public void run() {
		//El parámetro del siguiente método es un objeto (un Object, un String, un Integer...) no nulo sobre el cual se va a sincronizar.EL objeto mutex sería una especie de guardia de tráfico que decide quien pasa y quien no
		synchronized(mutex){ 
			try {
				variable-=10;			//90 80 70
				Thread.sleep(500);
				System.out.println("Variable1:" + variable);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Output:
Variable1:90.0
Variable1:80.0
Variable1:70.0

Implementar Runnable

Me creo una instancia de la clase Thread, pasándole como parámetro un objeto de una clase que implementa la interfaz Runnable.

Método Main de las siguientes implementaciones de hilos:
public class Main {

	public static void main(String args[]){
		Thread hilo1 = new Thread(new Hilo());
		Thread hilo2 = new Thread(new Hilo());
		Thread hilo3 = new Thread(new Hilo());
		
		hilo1.start();
		hilo2.start();
		hilo3.start();		
	}
}

Nota:
¿Por qué existe la interfaz Runnable?
Porque en Java sólo es posible heredar de una clase y por tanto a veces no será posible hereder de la clase Thread.

Sin sincronización

class Hilo implements Runnable{
	static double variable = 100;
	public void run() {
		try {
			variable-=10;			//70 70 70
			Thread.sleep(500);
			System.out.println("Variable1:" + variable);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

Output:
Variable1:70.0
Variable1:70.0
Variable1:70.0

Con sincronización

class Hilo implements Runnable{
	
	static final Object mutex = new Object();
	static double variable = 100;
	public void run() {
		synchronized(mutex){ //Aquí viene un objeto (un Object, un String, un Integer...) no nulo 	sobre el cual se va a sincronizar. Es posible usar cualquier objeto porque los métodos que hacen de semáforo pertenecen a la clase Object.
		//EL objeto mutex sería una especie de guardia de tráfico que decide quien pasa y quien no
			try {
				variable-=10;		//90 80	70
				Thread.sleep(500);
				System.out.println("Variable1:" + variable);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Output:
Variable1:90.0
Variable1:80.0
Variable1:70.0

Ejercicio: hilos

Tenemos una aplicación a la que se conectan de manera concurrente 2 personas (2 hilos). Cada una de estas personas debe realizar 3 operaciones. Suponemos que cada una de estas operaciones tardará 10 milisegundos en realizarse.

Representaremos las 3 operaciones que realizará cada una de estas personas con el siguiente código:

for (int i = 0; i < 3; i++) {
	System.out.println("operación " + i);
	Thread.sleep(10);
}

Si no sincronizamos este código veremos el código de cada una de estas dos personas se entremezclará, dando lugar a unas trazas como estas:

operación 0
operación 0
operación 1
operación 1
operación 2
operación 2

Queremos sincronizar el código para que las operaciones de cada persona no se entremezclen. Tras la sincronización, las trazas deberían quedar así:

operación 0
operación 1
operación 2
operación 0
operación 1
operación 2

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.