Para empezar este artículo antes debemos tener claro algunos conceptos.
¿Qué son los hilos (o THREADS)?
En sistemas operativos, un hilo (del inglés thread), hebra (del inglés fiber), proceso ligero o subproceso es una secuencia de tareas encadenadas muy pequeña que puede ser ejecutada por un sistema operativo.
Los distintos hilos de ejecución comparten una serie de recursos tales como el espacio de memoria, los archivos abiertos, la situación de autenticación, etc. Esta técnica permite simplificar el diseño de una aplicación que debe llevar a cabo distintas funciones simultáneamente.
Un hilo es simplemente una tarea que puede ser ejecutada al mismo tiempo que otra tarea.
Los hilos de ejecución que comparten los mismos recursos, sumados a estos recursos. El hecho de que los hilos de ejecución de un mismo proceso compartan los recursos hace que cualquiera de estos hilos pueda modificar estos recursos. Cuando un hilo modifica un dato en la memoria, los otros hilos acceden a ese dato modificado inmediatamente.
Lo que es propio de cada hilo es el contador de programa, la pila de ejecución y el estado de la CPU (incluyendo el valor de los registros).
Al igual que los procesos, los hilos poseen un estado de ejecución y pueden sincronizarse entre ellos para evitar problemas de compartición de recursos. Generalmente, cada hilo tiene una tarea específica y determinada, como forma de aumentar la eficiencia del uso del procesador.
Estados de un hilo
Los principales estados de los hilos son: Ejecución, Listo y Bloqueado. No tiene sentido asociar estados de suspensión de hilos ya que es un concepto de proceso. En todo caso, si un proceso está expulsado de la memoria principal (RAM), todos sus hilos deberán estarlo ya que todos comparten el espacio de direcciones del proceso.
Cambio de estados
- Creación: Cuando se crea un proceso se crea un hilo para ese proceso. Luego, este hilo puede crear otros hilos dentro del mismo proceso, proporcionando un puntero de instrucción y los argumentos del nuevo hilo. El hilo tendrá su propio contexto y su propio espacio de la columna, y pasará al final de los Listos.
- Bloqueo: Cuando un hilo necesita esperar por un suceso, se bloquea (salvando sus registros de usuario, contador de programa y punteros de pila). Ahora el procesador podrá pasar a ejecutar otro hilo que esté al principio de los Listos mientras el anterior permanece bloqueado.
- Desbloqueo: Cuando el suceso por el que el hilo se bloqueó se produce, el mismo pasa a la final de los Listos.
- Terminación: Cuando un hilo finaliza se liberan tanto su contexto como sus columnas.
Para más información sobre hilos, podeis visitar Wikipedia.
¿Qué es la programación concurrente?
La computación concurrente es la simultaneidad en la ejecución de múltiples tareas interactivas. Estas tareas pueden ser un conjunto de procesos o hilos de ejecución creados por un único programa. Las tareas se pueden ejecutar en una sola unidad central de proceso (multiprogramación), en varios procesadores o en una red de computadores distribuidos. La programación concurrente está relacionada con la programación paralela, pero enfatiza más la interacción entre tareas. Así, la correcta secuencia de interacciones o comunicaciones entre los procesos y el acceso coordinado de recursos que se comparten por todos los procesos o tareas son las claves de esta disciplina.
Creación y arranque de Hilos.
Se puede hacer de varias formas:
Creación de un hilo implementando la interfaz Runnable para que sea utilizada por una instancia de Thread:
- La interfaz Runnable proporciona la capacidad de añadir la funcionalidad de un hilo a una clase simplemente implementando dicha interfaz.
- En la utilización de la interfaz Runable, el método run() implementa la operación create conteniendo el código a ejecutar por el hilo.
- Dicho método contendrá el hilo de ejecución. Podemos verlo como el método main() en el hilo.
- El hilo finaliza cuando finaliza el método run().
Creación de un hilo implementando la interfaz Runnable para que sea utilizada por una instancia de Thread.
- Creamos una clase que implemente Runnable.
- Codificamos el método run() en dicha clase.
- Opción 1:
- Opción 2:
Creación de un hilo extendiendo la clase Thread.
- Extendiendo de la clase Thread mediante la creación de una subclase.
- La clase Thread es responsable de producir hilos funcionales para otras clases e implementa la interfaz Runnable.
Creación de un hilo usando una clase anonima
- Extendiendo de la clase Thread enviándole al constructor una clase que implementa la interfaz Runnable.
- A diferencia del caso anterior, la clase Runnable se declara de forma anónima.
¿Que son métodos sincronizados?
La palabra reservada synchronized se usa para indicar que ciertas partes del código, (habitualmente, una función miembro) están sincronizadas, es decir, que solamente un subproceso puede acceder a dicho método a la vez.
Cada método sincronizado posee una especie de llave que puede cerrar o abrir la puerta de acceso. Cuando un subproceso intenta acceder al método sincronizado mirará a ver si la llave está echada, en cuyo caso no podrá accederlo. Si método no tiene puesta la llave entonces el subproceso puede acceder a dicho código sincronizado.
EJEMPLO
Y como siempre, para verlo un poco mejor vamos a ver y explicar una aplicación sobre el tema.
En este caso, haremos una aplicación para resolver el conocido problema de la cena de filósofos, que nos viene a plantear:
En una mesa redonda hay N filósofos sentados. Tienen N palillos para comer arroz, estando cada palillo compartido por dos filósofos, uno a la izquierda y otro a la derecha. Los filósofos están sentados a la mesa pensando y, de vez en cuando, intentan comer. Para poder comer necesitan utilizar los dos palillos que hay a sus lados.

La lógica de los procesos que simularemos es la siguiente:
• Imprimir un mensaje por pantalla que indique que el filósofo i está pensando, siendo i su identificador.
• Pensar durante un tiempo aleatorio.
• Imprimir un mensaje indicando que el filósofo quiere comer.
• Intentar coger los palillos que necesita para comer. El filósofo 0 necesitará los palillos 0 y 1, el
filósofo 1, los palillos 1 y 2, y así sucesivamente.
• Cuando tenga el control de los palillos, imprimirá un mensaje indicando que está comiendo.
• El filósofo estará comiendo durante un tiempo aleatorio.
• Una vez que finalice de comer, dejará los palillos en su sitio.
• Volver al primer paso.
Crearemos dos clases, una llamada Cena y otra llamada Filosofo. Ambas descenderán de Thread.
La clase Cena, almacenará una instacia de la clase Filosofo y en su método run() llamaremos a los métodos comer() o pensar() del filosofo, añadiendo una instrucción para dormir el hilo un tiempo aleatorio.

En la clase Filosofo necesitamos guardar el numero de filósofos, un array boleano de palillos y también guardar el numero de filosos que están comiendo.
En el método synchronized comer (al que pasaremos la id del filósofo por parámetro) haremos las comprobaciones que necesita el filósofo para comer. Es decir si no hay palillos, o si el numero de filósofos comiendo es igual que el total de ellos menos 1, mandaremos esperar al hilo.
Si no tiene que esperar, pondremos los palillos a falso y añadiremos un filósofo a comiendo. También imprimiremos un mensaje para saber cual está comiendo.

En el método synchronized dejarDeComer pondremos a verdadero los dos palillos y restaremos un filósofo a comiendo. Imprimeros un mensaje para saber quien está pensando y a su vez despertaremos a todos los hilos para que puedan seguir comiendo.

En el main lo unico que haremos será indicar el numero de filósofos y inicializarlos.

El código completo lo teneis en: PSP_Cena_Filosofos.
Si quereis más ejemplos podeis mirar los códigos de: PSP_Productor_Consumidor_Synchronized, PSP_Parking_Coches_camiones, PSP_Ensamblador_Tuercas_Tornillos_Sincronizado.