Introducción a los Hilos:
- Un hilo es una vía de ejecución independiente
- Los programas en ejecución tienen muchos procesos en segundo plano como el hilo principal, young.gc, full.gc()
- main es el hilo principal, punto de entrada del sistema, utilizado para ejecutar todo el programa
- En un proceso, si se abren múltiples hilos, la ejecución de los hilos es gestionada por el programador, cuyo orden no puede ser intervenido por el humano
- Al operar sobre el mismo recurso, existen problemas de competencia de recursos, que requieren control de concurrencia
- Los hilos traen sobrecargas adicionales, como el tiempo de programación de CPU y la sobrecarga del control de concurrencia
- Cada hilo interactúa en su propia memoria de trabajo, y un control inadecuado de la memoria puede causar inconsistencia de datos
Implementación de Hilos:
Extensión de la clase Thread
/**
* Ejemplo de extensión de la clase Thread
*/
public class EjemploHilo extends Thread {
// Punto de entrada del hilo
@Override
public void run() {
// Cuerpo del hilo
for (int i = 0; i < 10; i++) {
System.out.println("Procesando imagen " + i);
}
}
public static void main(String[] args) {
EjemploHilo hiloPrueba = new EjemploHilo();
hiloPrueba.start(); // Inicia el hilo, no necesariamente inmediatamente, requiere programación de CPU
hiloPrueba.run(); // Ejecuta inmediatamente el hilo
for (int i = 0; i < 10; i++) {
System.out.println("Ejecutando el método main " + i);
}
}
}
Descarga de imágenes con Thread
public class DescargaImagen extends Thread {
private String url;
private String nombreArchivo;
public DescargaImagen(String url, String nombreArchivo) {
this.url = url;
this.nombreArchivo = nombreArchivo;
}
@Override
public void run() {
DescargadorWeb descargador = new DescargadorWeb();
descargador.descargar(url, nombreArchivo);
System.out.println("Descargado archivo: " + nombreArchivo);
}
public static void main(String[] args) {
DescargaImagen hilo1 = new DescargaImagen("https://ejemplo.com/imagen1.jpg", "foto1.jpg");
DescargaImagen hilo2 = new DescargaImagen("https://ejemplo.com/imagen2.jpg", "foto2.jpg");
DescargaImagen hilo3 = new DescargaImagen("https://ejemplo.com/imagen3.jpg", "foto3.jpg");
hilo1.start();
hilo2.start();
hilo3.start();
}
}
class DescargadorWeb {
public void descargar(String url, String nombreArchivo) {
try {
FileUtils.copyURLToFile(new URL(url), new File(nombreArchivo));
System.out.println("Descarga completada");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Implementación de la interfaz Runnable
public class ImplementacionRunnable implements Runnable {
public static void main(String[] args) {
ImplementacionRunnable runnable = new ImplementacionRunnable();
new Thread(runnable).run();
for (int i = 0; i < 10; i++) {
System.out.println("Procesando en el hilo principal " + i);
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Ejecutando tareas en segundo plano " + i);
}
}
}
Problemas de Concurrencia Inicial
/**
* Ejemplo de implementación de la interfaz Runnable
* La ejecución del hilo requiere envolverlo en new Thread(ImplementacionRunnable)
* Se recomienda oficialmente la implementación de la interfaz Runnable
*/
public class SistemaReservas implements Runnable {
// Simulación de asientos disponibles
private int asientosDisponibles = 100;
@Override
public void run() {
while (true) {
// Salir cuando no queden asientos
if (asientosDisponibles <= 0) {
break;
}
// Simular demora
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Bloquear el acceso a los asientos
synchronized ((Object)asientosDisponibles) {
// Verificar nuevamente
if (asientosDisponibles <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + " reservó el asiento número " + asientosDisponibles--);
}
}
}
public static void main(String[] args) {
SistemaReservas sistema = new SistemaReservas();
new Thread(sistema, "Cliente1").start();
new Thread(sistema, "Cliente2").start();
new Thread(sistema, "Cliente3").start();
}
}
Carrera entre Tortuga y Conejo
public class Carrera implements Runnable{
private static String Ganador;
// Contenido de la carrera
public void run(){
// Si el hilo es el conejo, hacer que descanse 20ms
if(Thread.currentThread().getName().equals("Conejo")){
try{
Thread.sleep(20);
}catch(RuntimeException | InterruptedException e){
e.printStackTrace();
}
}
for(int i = 1; i <= 10; i++){
System.out.println(Thread.currentThread().getName() + " avanzó " + i + " pasos");
// Verificar si la carrera ha terminado
Boolean terminada = verificarFin(i);
if (terminada){
break;
}
}
}
public static void main(String[] args){
Carrera carrera = new Carrera();
new Thread(carrera, "Conejo").start();
new Thread(carrera, "Tortuga").start();
}
public Boolean verificarFin(int paso){
// Si hay un ganador, detener la carrera
if(Ganador != null){
return true;
}
// Si alguien ha llegado a 10 pasos, detener la carrera
if(paso == 10){
Ganador = Thread.currentThread().getName();
System.out.println("El ganador es " + Ganador);
return true;
}
return false;
}
}
Interfaces Funcionales y Expresiones Lambda
Antes de Java 8
public class EjemploLambda {
// Una interfaz con un solo método se llama interfaz funcional
interface ILike {
void like(int a);
}
public static void main(String[] args){
ILike like = new LikeImpl();
like.like(521);
}
static class LikeImpl implements ILike {
@Override
public void like(int a){
System.out.println("Me gustas " + a);
}
}
}
Simplificación 1: Clase Interna Estática
public class EjemploLambda2{
// Una interfaz con un solo método se llama interfaz funcional
interface ILike {
void like(int a);
}
public static void main(String[] args){
ILike like = new LikeImpl();
like.like(521);
}
static class LikeImpl implements ILike {
@Override
public void like(int a){
System.out.println("Me gustas " + a);
}
}
}
Simplificación 2: Clase Interna Local
public class EjemploLambda3{
// Una interfaz con un solo método se llama interfaz funcional
interface ILike {
void like(int a);
}
public static void main(String[] args){
class LikeImpl implements ILike{
@Override
public void like(int a){
System.out.println("Me gustas " + a);
}
}
ILike like = new LikeImpl();
like.like(521);
}
}
Simplificación 3: Clase Aónima
interface ILike {
void like(int a);
}
public class EjemploLambda4{
// Una interfaz con un solo método se llama interfaz funcional
public static void main(String[] args){
ILike like = new ILove(){
@Override
public void like(int a){
System.out.print("Me gustas " + a);
}
};
like.like(521);
}
}
Versión Final con Lambda
interface ILike {
void like(int a, int b);
}
public class EjemploLambdaFinal {
// Una interfaz con un solo método se llama interfaz funcional
public static void main(String[] args) {
ILike like = (a,b) -> {
System.out.println("Me gustas " + a + b);
};
like.like(520, 521);
}
}
Resumen de Lambda
- La interfaz solo puede tener un método
- Al ejecutar la lógica de negocio con lambda, deben llevar {}, a menos que la lógica sea solo una línea
- Se pueden eliminar los tipos de parámetro (todos) cuando hay múltiples parámetros
Estados del Hilo:
Creación: new Thread()
Listo (esperando programación de CPU): start()
Bloqueado (después de bloquearse, espera programación de CPU): sleep(), wait() o bloqueo síncrono
Sleep
/**
* Ejemplo con sleep para cuenta regresiva
*/
public class EjemploSleep{
public static void main(String[] args) throws InterruptedException {
cuentaRegresiva();
}
static void cuentaRegresiva() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println( num-- );
if (num <= 0){
break;
}
}
}
}
Join: hace que un hilo se bloquee, similar a un VIP que ejecuta primero
/**
* Ejemplo de join, hace que un hilo se bloquee
*/
public class EjemploJoin implements Runnable{
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println("VIP en acción, todos aparten! " + i);
}
}
public static void main(String[] args) throws InterruptedException {
EjemploJoin ejemploJoin = new EjemploJoin();
Thread hilo = new Thread(ejemploJoin);
hilo.start();
for (int i = 1; i < 101; i++) {
System.out.println("Método principal " + i);
if(i == 10){
hilo.join(); // Fuerza la ejecución del método run del hilo
}
}
}
}
Ejecución: start(), run()
Destrucción
No se recomienda usar stop(), die() u otros métodos obsoletos o no recomendados oficialmente.
Se recomienda usar métodos basados en banderas para detener hilos:
/*
* Ejemplo de detención de hilo usando banderas
* No se recomienda usar stop(), die() u otros métodos obsoletos
* Se recomienda usar métodos basados en banderas para detener hilos
*/
public class DetencionHilo implements Runnable {
public Boolean bandera = true;
@Override
public void run() {
int i = 0;
while (bandera) {
System.out.println("Paso " + i++);
}
}
// Método público para detener
public void detener() {
this.bandera = false;
}
public static void main(String[] args) {
DetencionHilo detencionHilo = new DetencionHilo();
new Thread(detencionHilo).start();
for (int i = 0; i < 100; i++) {
System.out.println("Método principal " + i);
if (i == 90) {
// Detener el hilo después de 90 ciclos
detencionHilo.detener();
System.out.println("El hilo debe detenerse");
}
}
}
}
Sincronización de Hilos
La sincronización de hilos es crucial para evitar condiciones de carrera y garantizar la consistencia de datos cuando múltiples hilos acceden a recursos compartidos.
Comunicación entre Hilos
La comunicación entre hilos permite que los hilos coordinen su trabajo y compartan información de manera segura.
Temas Avanzados
Lock en JDK 15
El paquete java.util.concurrent.locks proporciona interfaces y clases más avanzadas para el control de concurrencia que los mecanismos de sincronización tradicionales.