Threads en Java

COMPARTIR EN REDES SOCIALES

En esta entrada vamos a hablar sobre los Threads en Java. Lo primero que vamos a explicar es lo que son, para posteriormente entender que son a nivel de Hardware y finalmente veremos las diferentes formas de implementar y trabajar con Threads en Java. Realizaremos diferentes implementaciones y ejemplos prácticos, como en todas nuestras entradas.

¿ Qué es un Thread/Hilo ?

Un «thread», o «hilo» conocido en castellano, es un flujo de ejecución independiente que el procesador puede ejecutar de forma paralela. Antiguamente, los procesadores ejecutaban procesos, es decir, si nosotros abríamos una aplicación denominada X, el procesador creaba un proceso que representaba a X y ejecutaba dicho proceso. Esto cambio radicalmente con el paso del tiempo y la inclusión de los procesadores escalares y multinúcleo. Hoy en día los procesadores no ejecutan procesos directamente, ejecutan hilos que pertenecen a procesos, es decir, los procesos no son más que una abstracción que sirve para que el Sistema Operativo asigne recursos de memoria, cpu y disco a dicho proceso, pero lo que ejecuta realmente un procesador es un thread.

Esta aproximación nos permite que en un momento determinado en el tiempo haya tantos hilos ejecutándose como núcleos tengamos en el procesador, por lo que si tenemos un procesador con 8 núcleos seremos capaces de ejecutar 8 hilos al mismo tiempo, esto no quiere decir que no podamos crear cientos de threads, quiere decir que en un momento determinado de los 100 creados solo 8 se estarán ejecutando en paralelo.

¿ Qué nos permiten realizar los Threads ?

Los Threads nos permiten ejecutar tareas que puedan ser paralelizadas en un tiempo mucho menor. Veámoslo con un ejemplo teórico, supongamos que tenemos 2 matrices llamadas A y B de 9×9 cada una de ellas, es decir, 9 filas y 9 columnas:

Matriz A y Matriz B.
Matriz A y Matriz B.

Imaginad que queremos realizar una suma de matrices, nótese que tanto la matriz A como la matriz B son iguales. Si adoptásemos una estrategia sin utilizar Threads tendríamos un único thread, el de la aplicación para realizar la suma, veámoslo con código:

public static void main(String[] args) {
	//Creamos las matrices
	int [][] matrizA = new int[][] {{1,2,3,4,5,6,7,8,9}, {1,2,3,4,5,4,3,2,1}, {9,8,7,6,5,4,3,2,1}, {0,0,0,0,0,1,1,1,1}, {10,11,12,13,14,15,16,17,18}, {10,11,12,13,14,13,12,11,10}, {18,17,16,15,14,13,12,11,10}, {20,22,24,26,28,30,32,34,36}, {36,34,32,30,28,26,24,22,20}};
	int [][] matrizB = new int[][] {{1,2,3,4,5,6,7,8,9}, {1,2,3,4,5,4,3,2,1}, {9,8,7,6,5,4,3,2,1}, {0,0,0,0,0,1,1,1,1}, {10,11,12,13,14,15,16,17,18}, {10,11,12,13,14,13,12,11,10}, {18,17,16,15,14,13,12,11,10}, {20,22,24,26,28,30,32,34,36}, {36,34,32,30,28,26,24,22,20}};
	int [][] matrizResultado = new int [9][9];
	
	
	//Medimos el tiempo
	long iniTime = System.currentTimeMillis();
	//Realizamos la suma de matrices
	for (int fila = 0; fila < matrizA.length; fila++){
		for (int columna = 0; columna < matrizA[0].length; columna++) {
			matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
		}
	}
	long finTime = System.currentTimeMillis();
	
	System.out.println((finTime - iniTime) / 1000d);
	
	//Mostramos el resultado
	for (int fila = 0; fila < matrizA.length; fila++){
		for (int columna = 0; columna < matrizA[0].length; columna++) {
			System.out.print(matrizResultado[fila][columna] + "\t");
		}
		System.out.println();
	}

}

La salida de nuestro programa secuencial

2	4	6	8	10	12	14	16	18	
2	4	6	8	10	8	6	4	2	
18	16	14	12	10	8	6	4	2	
0	0	0	0	0	2	2	2	2	
20	22	24	26	28	30	32	34	36	
20	22	24	26	28	26	24	22	20	
36	34	32	30	28	26	24	22	20	
40	44	48	52	56	60	64	68	72	
72	68	64	60	56	52	48	44	40

Vamos ahora a adoptar una estrategia de realizar la misma operación con Threads:

  1. Crearemos 2 threads. Al thread 1 le asignaremos las primeras 5 filas de nuestras matrices, al segundo las 4 restantes.
  2. Esperaremos a que cada Thread haya realizado los cálculos.
  3. Mostraremos los resultados.

Nuestra clase ThreadSumaMatrices

public class ThreadSumaMatrices extends Thread{

	private int filaInicial;
	private int filaFinal;
	private int [][] matrizA;
	private int [][] matrizB;
	private int [][] matrizResultado;
	
	//Constructor de nuestro Thread de Suma de Matrices
	public ThreadSumaMatrices(int filaInicial, int filaFinal, int [][] matrizA, int [][] matrizB, int [][] matrizResultado) {
		this.filaInicial = filaInicial;
		this.filaFinal = filaFinal;
		this.matrizA = matrizA;
		this.matrizB = matrizB;
		this.matrizResultado = matrizResultado;
	}
	
	//Código que ejecuta nuestro Thread
	public void run() {
		for (int fila = filaInicial; fila < filaFinal; fila++) {
			for (int columna = 0; columna < matrizA[0].length; columna++) {
				matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
			}
		}
	}
	
}

Nuestro código Principal

public class Main {

	public static void main(String[] args) {
		//Creamos las matrices
		int [][] matrizA = new int[][] {{1,2,3,4,5,6,7,8,9}, {1,2,3,4,5,4,3,2,1}, {9,8,7,6,5,4,3,2,1}, {0,0,0,0,0,1,1,1,1}, {10,11,12,13,14,15,16,17,18}, {10,11,12,13,14,13,12,11,10}, {18,17,16,15,14,13,12,11,10}, {20,22,24,26,28,30,32,34,36}, {36,34,32,30,28,26,24,22,20}};
		int [][] matrizB = new int[][] {{1,2,3,4,5,6,7,8,9}, {1,2,3,4,5,4,3,2,1}, {9,8,7,6,5,4,3,2,1}, {0,0,0,0,0,1,1,1,1}, {10,11,12,13,14,15,16,17,18}, {10,11,12,13,14,13,12,11,10}, {18,17,16,15,14,13,12,11,10}, {20,22,24,26,28,30,32,34,36}, {36,34,32,30,28,26,24,22,20}};
		int [][] matrizResultado = new int [9][9];
			
		//Creamos los threads asignándoles las filas correspondientes
		ThreadSumaMatrices tSumaMatrices1 = new ThreadSumaMatrices(0, 5, matrizA, matrizB, matrizResultado);
		ThreadSumaMatrices tSumaMatrices2 = new ThreadSumaMatrices(5, 9, matrizA, matrizB, matrizResultado);
		
		//Iniciamos los threads
		tSumaMatrices1.start();
		tSumaMatrices2.start();
		
		//Esperamos a que terminen de realizar las sumas
		try {
			tSumaMatrices1.join();
			tSumaMatrices2.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//Mostramos el resultado
		for (int fila = 0; fila < matrizA.length; fila++){
			for (int columna = 0; columna < matrizA[0].length; columna++) {
				System.out.print(matrizResultado[fila][columna] + "\t");
			}
			System.out.println();
		}
	
	}
}

La salida de nuestro programa paralelo

2	4	6	8	10	12	14	16	18	
2	4	6	8	10	8	6	4	2	
18	16	14	12	10	8	6	4	2	
0	0	0	0	0	2	2	2	2	
20	22	24	26	28	30	32	34	36	
20	22	24	26	28	26	24	22	20	
36	34	32	30	28	26	24	22	20	
40	44	48	52	56	60	64	68	72	
72	68	64	60	56	52	48	44	40

Como podéis observar de una forma bastante sencilla hemos conseguido paralelizar nuestro programa para realizar la suma de nuestras matrices.

Vamos ahora a crear matrices lo suficientemente grandes para ver la mejora que se produce en tiempos al utilizar hilos. Crearemos matrices de 10000×10000. Ejecutaremos la suma de matrices de forma secuencia, con 2 threads y con 4 threads y veremos la mejora de tiempos que se produce que es bastante notable:

Veamos nuestro código:

public class Main {

	private static final int NUM_FILAS = 10000;
	private static final int NUM_COLUMNAS = 10000;

	
	public static void main(String[] args) {
		//Creamos las matrices
		int [][] matrizA = new int[NUM_FILAS][NUM_COLUMNAS]; 
		int [][] matrizB = new int[NUM_FILAS][NUM_COLUMNAS];
		int [][] matrizResultado = new int [NUM_FILAS][NUM_COLUMNAS];
			
		//Creamos los elementos
		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizA[fila][columna] = fila;
				matrizB[fila][columna] = fila;
			}
		}
		
		//Realizamos la suma secuencial;
		long iniTime = System.currentTimeMillis();
		
		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
			}
		}
		
		long finTime = System.currentTimeMillis();
		//Mostramos el tiempo que hemos tardado
		System.out.println("Tiempo Secuencial: " + (finTime - iniTime) / 1000d);
		
		
		iniTime = System.currentTimeMillis();
		
		//Creamos 2 threads asignándoles las filas correspondientes
		ThreadSumaMatrices tSumaMatrices1 = new ThreadSumaMatrices(0, 5000, matrizA, matrizB, matrizResultado);
		ThreadSumaMatrices tSumaMatrices2 = new ThreadSumaMatrices(5000, 10000, matrizA, matrizB, matrizResultado);
		
		//Iniciamos los threads
		tSumaMatrices1.start();
		tSumaMatrices2.start();
		
		//Esperamos a que terminen de realizar las sumas
		try {
			tSumaMatrices1.join();
			tSumaMatrices2.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finTime = System.currentTimeMillis();
		
		System.out.println("Tiempo con 2 Threads: " + (finTime - iniTime) / 1000d);

		//Ahora con 4 Threads
		iniTime = System.currentTimeMillis();
		
		//Creamos 2 threads asignándoles las filas correspondientes
		tSumaMatrices1 = new ThreadSumaMatrices(0, 2500, matrizA, matrizB, matrizResultado);
		tSumaMatrices2 = new ThreadSumaMatrices(2500, 5000, matrizA, matrizB, matrizResultado);
		ThreadSumaMatrices tSumaMatrices3 = new ThreadSumaMatrices(5000, 7500, matrizA, matrizB, matrizResultado);
		ThreadSumaMatrices tSumaMatrices4 = new ThreadSumaMatrices(7500, 10000, matrizA, matrizB, matrizResultado);

		//Iniciamos los threads
		tSumaMatrices1.start();
		tSumaMatrices2.start();
		tSumaMatrices3.start();
		tSumaMatrices4.start();
		
		//Esperamos a que terminen de realizar las sumas
		try {
			tSumaMatrices1.join();
			tSumaMatrices2.join();
			tSumaMatrices3.join();
			tSumaMatrices4.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finTime = System.currentTimeMillis();
		
		System.out.println("Tiempo con 4 Threads: " + (finTime - iniTime) / 1000d);
		
	}
}

Visto el ejemplo introductorio vamos a ver las diferentes formas de crear threads.

Creando Threads extendiendo de la clase Thread

La primera forma que vamos a ver de crear Threads consiste en extender de la clase «Thread» e implementar el método «run». Consiste en crear una clase que utilizando herencia que extienda de la clase «Thread» y meter el código que queramos que ejecute nuestro hilo en el método «run»:

Esta aproximación nos permite pasar los parámetros necesarios para que pueda funcionar nuestro hilo en el constructor de la clase. Nuestro ejemplo de Suma de Matrices lo hemos hecho de esta forma:

public class ThreadSumaMatrices extends Thread{

	private int filaInicial;
	private int filaFinal;
	private int [][] matrizA;
	private int [][] matrizB;
	private int [][] matrizResultado;
	
	//Constructor de nuestro Thread de Suma de Matrices
	public ThreadSumaMatrices(int filaInicial, int filaFinal, int [][] matrizA, int [][] matrizB, int [][] matrizResultado) {
		this.filaInicial = filaInicial;
		this.filaFinal = filaFinal;
		this.matrizA = matrizA;
		this.matrizB = matrizB;
		this.matrizResultado = matrizResultado;
	}
	
	//Código que ejecuta nuestro Thread
	public void run() {
		for (int fila = filaInicial; fila < filaFinal; fila++) {
			for (int columna = 0; columna < matrizA[0].length; columna++) {
				matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
			}
		}
	}
	
}

Aquí creamos atributos de clase para que nuestros datos estén disponibles en el método «run» y los asignamos mediante el constructor.

Una vez realizada nuestra clase Thread solo nos falta instanciarla, ejecutar el método «start» y esperar a que acabe nuestro hilo de ejecutarse.

Instanciando la clase

Para instanciar nuestro hilo realmente es muy sencillo, simplemente creamos un nuevo objeto de la clase que hemos creado, y entre sus parámetros en el constructor le pasamos todos los datos necesarios que necesita el método «run» para poder realizar nuestra tarea:

ThreadSumaMatrices tSumaMatrices3 = new ThreadSumaMatrices(5000, 7500, matrizA, matrizB, matrizResultado);

Es importante destacar que esto no hará que el hilo se ejecute, simplemente creará la instancia. Para que nuestro hilo empiece a funcionar es necesario llamar al método «start».

Llamando al método start

Una vez instanciada nuestra clase Thread solo tenemos que llamar al método «start» para que nuestro hilo comience a ejecutarse:

tSumaMatrices3.start();

Esto pondrá en funcionamiento nuestro Thread, pero y ¿ Cómo sabemos cuando ha terminado el mismo ?

Utilizando el método join

El método «join» nos permite que otro hilo de ejecución espere a que finalice nuestro Thread. En el ejemplo de la suma de matrices hemos utilizado el hilo principal de la aplicación, es decir, el hilo donde se ejecuta nuestro «main» para esperar a que terminen los threads que hemos creado en el mismo método.

try {
	tSumaMatrices1.join();
	tSumaMatrices2.join();
	tSumaMatrices3.join();
	tSumaMatrices4.join();
} catch (InterruptedException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

La interface Runnable

La segunda forma que vamos a explicar es mediante el uso de la interfaz «Runnable», esta forma se utiliza cuando no podemos extender de la clase Thread porque ya hemos extendido de otro clase y como sabéis, no podemos tener herencia múltiple en Java, es decir no podemos extender de una clase y de «Thread».

Veamos el código y luego expliquemos su funcionamiento:

Nuestra clase RunnableSumaMatrices

public class RunnableSumaMatrices extends OtraClase implements Runnable{

	private int filaInicial;
	private int filaFinal;
	private int [][] matrizA;
	private int [][] matrizB;
	private int [][] matrizResultado;
	
	//Constructor de nuestra clase Runnable de Suma de Matrices
	public RunnableSumaMatrices(int filaInicial, int filaFinal, int [][] matrizA, int [][] matrizB, int [][] matrizResultado) {
		this.filaInicial = filaInicial;
		this.filaFinal = filaFinal;
		this.matrizA = matrizA;
		this.matrizB = matrizB;
		this.matrizResultado = matrizResultado;
	}
	
	//Código que ejecuta nuestro Thread
		public void run() {
			for (int fila = filaInicial; fila < filaFinal; fila++) {
				for (int columna = 0; columna < matrizA[0].length; columna++) {
					matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
				}
			}
		}
}

Nuestra clase Main

public class MainRunnable {

	private static final int NUM_FILAS = 10000;
	private static final int NUM_COLUMNAS = 10000;

	public static void main(String[] args) {
		// Creamos las matrices
		int[][] matrizA = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizB = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizResultado = new int[NUM_FILAS][NUM_COLUMNAS];

		// Creamos los elementos
		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizA[fila][columna] = fila;
				matrizB[fila][columna] = fila;
			}
		}

		// Realizamos la suma secuencial;
		long iniTime = System.currentTimeMillis();

		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
			}
		}

		long finTime = System.currentTimeMillis();
		// Mostramos el tiempo que hemos tardado
		System.out.println("Tiempo Secuencial: " + (finTime - iniTime) / 1000d);

		iniTime = System.currentTimeMillis();
		// Creamos 2 runnable y los encapsulamos en 2 Threads
		RunnableSumaMatrices r1 = new RunnableSumaMatrices(0, 5000, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r2 = new RunnableSumaMatrices(5000, 10000, matrizA, matrizB, matrizResultado);
		// Encapsulamos nuestra clase que implementa Runnable
		Thread tSumaMatrices1 = new Thread(r1);
		Thread tSumaMatrices2 = new Thread(r1);
		// Iniciamos nuestros Threads
		// Iniciamos los threads
		tSumaMatrices1.start();
		tSumaMatrices2.start();

		// Esperamos a que terminen de realizar las sumas
		try {
			tSumaMatrices1.join();
			tSumaMatrices2.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finTime = System.currentTimeMillis();

		System.out.println("Tiempo con 2 Threads: " + (finTime - iniTime) / 1000d);

		iniTime = System.currentTimeMillis();
		// Ahora con 4 runnable y 4 threads
		RunnableSumaMatrices r3 = new RunnableSumaMatrices(0, 2500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r4 = new RunnableSumaMatrices(2500, 5000, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r5 = new RunnableSumaMatrices(5000, 7500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r6 = new RunnableSumaMatrices(7500, 10000, matrizA, matrizB, matrizResultado);
		// Encapsulamos los Runnable en los Threads
		Thread tSumaMatrices3 = new Thread(r3);
		Thread tSumaMatrices4 = new Thread(r4);
		Thread tSumaMatrices5 = new Thread(r5);
		Thread tSumaMatrices6 = new Thread(r6);

		// Iniciamos los threads
		tSumaMatrices3.start();
		tSumaMatrices4.start();
		tSumaMatrices5.start();
		tSumaMatrices6.start();

		// Esperamos a que terminen de realizar las sumas
		try {
			tSumaMatrices3.join();
			tSumaMatrices4.join();
			tSumaMatrices5.join();
			tSumaMatrices6.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finTime = System.currentTimeMillis();

		System.out.println("Tiempo con 4 Threads: " + (finTime - iniTime) / 1000d);
	}

}

La clave aquí esta en crear un Objeto de una clase que implemente la interfaz Runnable y encapsularlo en un Thread, todo lo demás es igual, simplemente llamamos al método «start» y al «join» para esperar a que termine:

RunnableSumaMatrices r1 = new RunnableSumaMatrices(0, 5000, matrizA, matrizB, matrizResultado);
Thread tSumaMatrices1 = new Thread(r1);

La salida de nuestro programa

Tiempo Secuencial: 0.121
Tiempo con 2 Threads: 0.082
Tiempo con 4 Threads: 0.052

Pools de Threads

Imagen cogida de la Página web de https://www.baeldung.com/thread-pool-java-and-guava

En la imagen anterior podéis ver en que consisten los Threads de Pools, por una parte, a la izquierda de todo tenemos los emisores de tareas, estos añadirán tareas para ser ejecutadas. En la parte central tenemos el «Executor Service», este tendrá una cola donde irá encolando las tareas que le llegan y luego se encargará de asignar cada una de ellas a uno de los Threads que tengamos disponible en nuestro Thread Pool.

¿ Qué conseguimos con esta aproximación ?

Crear threads tiene una sobrecarga de computación, si en nuestros programas estamos constantemente creando threads para ejecutar pequeñas tareas lo mejor es reutilizar los Threads, y esto es exactamente lo que nos permite realizar un pool de threads. Los threads se crean al inicio y posteriormente se reutilizan los ya creados.

Pool de Threads con un número finito de Threads

En este caso creamos un número fijo de Threads, si no tenemos threads disponibles nuestras tareas esperaran en la cola de tareas a procesar, veamos el código y luego expliquemos el código:

La clase RunnableSumaMatrices

public class RunnableSumaMatrices extends OtraClase implements Runnable{

	private int filaInicial;
	private int filaFinal;
	private int [][] matrizA;
	private int [][] matrizB;
	private int [][] matrizResultado;
	
	//Constructor de nuestra clase Runnable de Suma de Matrices
	public RunnableSumaMatrices(int filaInicial, int filaFinal, int [][] matrizA, int [][] matrizB, int [][] matrizResultado) {
		this.filaInicial = filaInicial;
		this.filaFinal = filaFinal;
		this.matrizA = matrizA;
		this.matrizB = matrizB;
		this.matrizResultado = matrizResultado;
	}
	
	//Código que ejecuta nuestro Thread
		public void run() {
			for (int fila = filaInicial; fila < filaFinal; fila++) {
				for (int columna = 0; columna < matrizA[0].length; columna++) {
					matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
				}
			}
			
			System.out.println("Soy un hilo y he acabado de realizar mis cálculos");
		}
	
}

Nuestra clase Main

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolExecutors {

	private static final int NUM_FILAS = 10000;
	private static final int NUM_COLUMNAS = 10000;
	
	public static void main(String[] args) {
		// Creamos las matrices
		int[][] matrizA = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizB = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizResultado = new int[NUM_FILAS][NUM_COLUMNAS];

		// Creamos los elementos
		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizA[fila][columna] = fila;
				matrizB[fila][columna] = fila;
			}
		}
		
		//Creamos nuestro pool de threads, estableciendo 10 como número de threads
		ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
		
		//Creamos las tareas runnable
		RunnableSumaMatrices r3 = new RunnableSumaMatrices(0, 2500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r4 = new RunnableSumaMatrices(2500, 5000, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r5 = new RunnableSumaMatrices(5000, 7500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r6 = new RunnableSumaMatrices(7500, 10000, matrizA, matrizB, matrizResultado);
		
		//Emitimos las tareas
		Future f1 = executor.submit(r3);
		Future f2 = executor.submit(r4);
		Future f3 = executor.submit(r5);
		Future f4 = executor.submit(r6);
		
		//Esperamos a que terminen nuestros Threads
		
		try {
			f1.get();
			f2.get();
			f3.get();
			f4.get();
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("Todos nuestros Threads han terminado");
		
		//Cerramos nuestro Executor
        executor.shutdown();
	}

}

Lo primero que realizamos es crear nuestro pool de Threads, en este caso con 10 hilos, luego crearemos nuestras tareas Runnable de la misma forma que hemos hecho en el ejemplo anterior, posteriormente las mandamos a ejecutar. En este punto la función «submit» devuelve un objeto de tipo «Future», esto nos permitirá averiguar cuando han terminado nuestras tareas de ejecutarse.

Llamando a los diferentes «get» de los «Future» nos aseguramos de que imprimiremos «Todos nuestros Threads han terminado» una vez hayan terminado, es realmente un «join» de los ejemplo anteriores pero con otro mecanismo.

La ejecución

Soy un hilo y he acabado de realizar mis cálculos
Soy un hilo y he acabado de realizar mis cálculos
Soy un hilo y he acabado de realizar mis cálculos
Soy un hilo y he acabado de realizar mis cálculos
Todos nuestros Threads han terminado

Pool de Threads bajo demanda

Esta aproximación difiere de la anterior, ya que aquí no creamos un número fijo de Threads al instanciar nuestro Pool, sino que se van creando bajo demanda. Los ya creados se mantienen un tiempo(60 segundos por defecto) en memoria sin ser borrados para ser reutilizados y no tener que volver a crearlos. Esta aproximación es mucho más eficiente que la anterior:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolCached {

	private static final int NUM_FILAS = 10000;
	private static final int NUM_COLUMNAS = 10000;
	
	public static void main(String[] args) {
		// Creamos las matrices
		int[][] matrizA = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizB = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizResultado = new int[NUM_FILAS][NUM_COLUMNAS];

		// Creamos los elementos
		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizA[fila][columna] = fila;
				matrizB[fila][columna] = fila;
			}
		}
		
		//Creamos nuestro pool de threads, estableciendo 10 como número de threads
		ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
		
		//Creamos las tareas runnable
		RunnableSumaMatrices r3 = new RunnableSumaMatrices(0, 2500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r4 = new RunnableSumaMatrices(2500, 5000, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r5 = new RunnableSumaMatrices(5000, 7500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r6 = new RunnableSumaMatrices(7500, 10000, matrizA, matrizB, matrizResultado);
		
		//Emitimos las tareas
		Future f1 = executor.submit(r3);
		Future f2 = executor.submit(r4);
		Future f3 = executor.submit(r5);
		Future f4 = executor.submit(r6);
		
		//Esperamos a que terminen nuestros Threads
		
		try {
			f1.get();
			f2.get();
			f3.get();
			f4.get();
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("Todos nuestros Threads han terminado");
		
		//Cerramos nuestro Executor
        executor.shutdown();
	}

}

El funcionamiento es igual que el anterior, pero en este caso creamos un pool de threads cacheable:

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();

¿ Y si queremos obtener datos de nuestro tarea cuando ha terminado ?

Muy sencillo, lo hacemos a través de la tarea Runnable que hemos creado una vez sepamos con certeza que se ha terminado de ejecutar:

Nuestra clase Runnable modificada con un getter

public class RunnableSumaMatrices extends OtraClase implements Runnable{

	private int filaInicial;
	private int filaFinal;
	private int [][] matrizA;
	private int [][] matrizB;
	private int [][] matrizResultado;
	private boolean haTerminado = false;
	
	//Constructor de nuestra clase Runnable de Suma de Matrices
	public RunnableSumaMatrices(int filaInicial, int filaFinal, int [][] matrizA, int [][] matrizB, int [][] matrizResultado) {
		this.filaInicial = filaInicial;
		this.filaFinal = filaFinal;
		this.matrizA = matrizA;
		this.matrizB = matrizB;
		this.matrizResultado = matrizResultado;
	}
	
	public boolean haTerminado() {
		return haTerminado;
	}
	
	//Código que ejecuta nuestro Thread
		public void run() {
			for (int fila = filaInicial; fila < filaFinal; fila++) {
				for (int columna = 0; columna < matrizA[0].length; columna++) {
					matrizResultado[fila][columna] = matrizA[fila][columna] + matrizB[fila][columna];
				}
			}
			
			haTerminado = true;
			System.out.println("Soy un hilo y he acabado de realizar mis cálculos");
		}
	
}

Nuestra clase Main

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolCached {

	private static final int NUM_FILAS = 10000;
	private static final int NUM_COLUMNAS = 10000;
	
	public static void main(String[] args) {
		// Creamos las matrices
		int[][] matrizA = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizB = new int[NUM_FILAS][NUM_COLUMNAS];
		int[][] matrizResultado = new int[NUM_FILAS][NUM_COLUMNAS];

		// Creamos los elementos
		for (int fila = 0; fila < NUM_FILAS; fila++) {
			for (int columna = 0; columna < NUM_COLUMNAS; columna++) {
				matrizA[fila][columna] = fila;
				matrizB[fila][columna] = fila;
			}
		}
		
		//Creamos nuestro pool de threads, estableciendo 10 como número de threads
		ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
		
		//Creamos las tareas runnable
		RunnableSumaMatrices r3 = new RunnableSumaMatrices(0, 2500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r4 = new RunnableSumaMatrices(2500, 5000, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r5 = new RunnableSumaMatrices(5000, 7500, matrizA, matrizB, matrizResultado);
		RunnableSumaMatrices r6 = new RunnableSumaMatrices(7500, 10000, matrizA, matrizB, matrizResultado);
		
		//Emitimos las tareas
		Future f1 = executor.submit(r3);
		Future f2 = executor.submit(r4);
		Future f3 = executor.submit(r5);
		Future f4 = executor.submit(r6);
		
		//Esperamos a que terminen nuestros Threads
		
		try {
			f1.get();
			f2.get();
			f3.get();
			f4.get();
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(r3.haTerminado());
		System.out.println(r4.haTerminado());
		System.out.println(r5.haTerminado());
		System.out.println(r6.haTerminado());

		System.out.println("Todos nuestros Threads han terminado");
		
		//Cerramos nuestro Executor
        executor.shutdown();
	}

}

La salida de nuestro programa

Soy un hilo y he acabado de realizar mis cálculos
Soy un hilo y he acabado de realizar mis cálculos
Soy un hilo y he acabado de realizar mis cálculos
Soy un hilo y he acabado de realizar mis cálculos
true
true
true
true
Todos nuestros Threads han terminado

Y esto ha sido todo sobre Threads, espero que os haya gustado y que os sea útil de algún modo.

Dejad un comentario para mostrar vuestro apoyo en la caja de comentarios, no es necesario que oe registréis ni nada por el estilo, solo poner un correo electrónico.

Un saludo


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *