Programación concurrente, tambien se puede conocer con el termino de ejecución en paralelo y esta hace referencia a la ejecución de multiples tareas al mismo tiempo.

Anteriormente en sistemas operativos antiguos no era posible ejecutar multiples tareas al mismo tiempo. Sin embargo, se tuvo la necesidad de realizar esto para un mayor aprovechamiento de recursos del sistema. Ademas el termino paralelismo anteriormente no era posible, debido a que los procesadores solo tenian un nucleo por lo que simplemente a simple vista parecia que ejecutaba programas al mismo tiempo pero en realidad lo que sucede a nivel de sistema es que el gestor de procesos se encargaba de repartir un tiempo a cada proceso.

Sin embargo, actualmente con el avance de la tecnologia existen procesadores con multiples nucleos y aqui realmente ocurre el paralelismo. Afortunadamente JAVA tiene su propia maquina virtual encargada de administrar la ejecucion de procesos creados por el propio lenguaje, esto facilita la tarea de creación. Pero la cosa no solo termina aqui, ya que pueden surgir varios problemas con la programacion concurrente una de ellas es la incongruencia de datos.

Ejemplo:

Tenemos una variable contador = 0, imagina que estas lanzando multiples hilos para incrementar en uno el valor de la variable. Entonces entra en juego el hilo 1 e incrementa contador = 1 (pero aun no escribe ese valor en la variable), entonces el gestor del sistema operativo le dice: oye hilo 1, espera que te voy a suspender y ahora entra en juego el hilo 2, la variable contador aun tiene el valor cero, por lo que el hilo 2 toma ese valor lo incremente en uno y este escribe el valor siendo contador = 1.

El gestor del sistema operativo decide retomar el hilo 1 y este vuelve a escribir el numero 1 y se supone que la variable contador debe ser igual a 2.

Este es un problema muy comun al utilizar Thread (hilos), por lo que debes solucionar el problema sincronizando la ejecución de estos. Sin embargo, en este articulo no explicare el procedimiento (lo menciono para que tengas una idea de lo que puede suceder).

Comencemos a crear hilos, primero explicare a crearlos utilizando la clase Thread (heredando de esta).

package hilos;

public class HiloUtilizandoThread extends Thread {
    
    public HiloUtilizandoThread(String tarea) {
        this.tarea = tarea;
    }
    
    @Override
    public void run() {
        System.out.println(tarea);
    }
    
    private String tarea;
    
}

El metodo run() es el corazón del subproceso encargado de ejecutar la acción (aqui debemos poner el código a ejecutar). Sin embargo, no debe ejecutar los hijos mediante este metodo, pues no estaria aplicando programación concurrente (mas adelante mencionare como lanzar hilos).

Otra forma de implementar Hilos es utilizando la clase Runnable, existen dos formas de crear hilos. Debido a que,  si una clase donde quieras crear hilos ya este heredando de otra clase (por lo cual no podras heredar de Thread pues sabemos que Java no permite herencia multiple). Sin embargo, JAVA permite interfaces multiples y es por ese motivo que se pueden implementar hilos en 2 formas. El código para implementar hilos utilizando Runnable es el siguiente:

package hilos;

public class HiloUtilizandoRunnable implements Runnable {
    
    public HiloUtilizandoRunnable(String tarea) {
        this.tarea = tarea;
    }

    @Override
    public void run() {
        System.out.println(tarea);
    }
    
    private String tarea;
    
}

Ahora debemos lanzar los hilos creados, podemos hacerlo con el código mostrado a continuación:

package hilos;

public class HilosMain {

    public static void main(String[] args) {
        //Creacion de hilos utilizando la clase Thread
        HiloUtilizandoThread hilo1 = new HiloUtilizandoThread("Tarea 1 con Thread");
        HiloUtilizandoThread hilo2 = new HiloUtilizandoThread("Tarea 2 con Thread");
        hilo1.start();
        hilo2.start();
        //Creacion de hilos utilizando la interfaz Runnable
        Thread hiloR1 = new Thread(new HiloUtilizandoRunnable("Tarea 1 con Runnable"));
        Thread hiloR2 = new Thread(new HiloUtilizandoRunnable("Tarea 2 con Runnable"));
        hiloR1.start();
        hiloR2.start();
    }
    
}

El sistema operativo tiene una cola encargada de gestionar los procesos en ejecución, el metodo start() pone los hilos creados en la cola del sistema operativo y este se encarga de gestionar la ejecución (el orden de ejecución de hilos, depende del sistema operativo).