La sincronización es un concepto crucial en la programación concurrente, especialmente cuando múltiples hilos acceden y modifican recursos compartidos. Sin una adecuada sincronización, los programas pueden comportarse de manera impredecible y producir resultados incorrectos. En esta sección, aprenderemos sobre los mecanismos de sincronización en Java y cómo utilizarlos para asegurar que nuestros programas concurrentes funcionen correctamente.
Conceptos Clave
- Condiciones de Carrera: Ocurren cuando dos o más hilos acceden a un recurso compartido simultáneamente y al menos uno de los accesos es una escritura.
- Bloqueo (Lock): Un mecanismo que permite a un hilo obtener acceso exclusivo a un recurso compartido.
- Monitores: Cada objeto en Java puede actuar como un monitor, permitiendo que los métodos sincronizados controlen el acceso a los recursos compartidos.
- Palabra Clave
synchronized: Utilizada para definir métodos o bloques de código que deben ser ejecutados por un solo hilo a la vez.
Métodos Sincronizados
Ejemplo de Método Sincronizado
public class Contador {
private int cuenta = 0;
public synchronized void incrementar() {
cuenta++;
}
public synchronized int obtenerCuenta() {
return cuenta;
}
}En este ejemplo, los métodos incrementar y obtenerCuenta están sincronizados, lo que significa que solo un hilo puede ejecutarlos a la vez.
Explicación
synchronized: La palabra clavesynchronizedasegura que el método solo puede ser ejecutado por un hilo a la vez. Si un hilo está ejecutando un método sincronizado, otros hilos que intenten ejecutar cualquier método sincronizado en el mismo objeto serán bloqueados hasta que el primer hilo termine.
Bloques Sincronizados
A veces, no es necesario sincronizar un método completo. En su lugar, podemos sincronizar solo una parte del código utilizando bloques sincronizados.
Ejemplo de Bloque Sincronizado
public class Contador {
private int cuenta = 0;
private final Object lock = new Object();
public void incrementar() {
synchronized (lock) {
cuenta++;
}
}
public int obtenerCuenta() {
synchronized (lock) {
return cuenta;
}
}
}Explicación
- Bloque
synchronized: En este ejemplo, solo las secciones críticas del código están sincronizadas. El objetolockse utiliza para asegurar que solo un hilo pueda ejecutar el bloque sincronizado a la vez.
Ejercicio Práctico
Ejercicio 1: Sincronización de un Contador
Objetivo: Implementar un contador seguro para hilos utilizando métodos sincronizados.
Instrucciones:
- Crear una clase
ContadorSegurocon un campocuentade tipoint. - Implementar un método
incrementarque incrementecuentade manera segura utilizando la palabra clavesynchronized. - Implementar un método
obtenerCuentaque devuelva el valor decuentade manera segura utilizando la palabra clavesynchronized. - Crear una clase
HiloContadorque implementeRunnabley en su métodorunincremente el contador 1000 veces. - Crear una clase
Mainque inicie 10 hilos deHiloContadory luego imprima el valor final del contador.
Solución
public class ContadorSeguro {
private int cuenta = 0;
public synchronized void incrementar() {
cuenta++;
}
public synchronized int obtenerCuenta() {
return cuenta;
}
}
public class HiloContador implements Runnable {
private ContadorSeguro contador;
public HiloContador(ContadorSeguro contador) {
this.contador = contador;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
contador.incrementar();
}
}
}
public class Main {
public static void main(String[] args) {
ContadorSeguro contador = new ContadorSeguro();
Thread[] hilos = new Thread[10];
for (int i = 0; i < 10; i++) {
hilos[i] = new Thread(new HiloContador(contador));
hilos[i].start();
}
for (int i = 0; i < 10; i++) {
try {
hilos[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Valor final del contador: " + contador.obtenerCuenta());
}
}Explicación de la Solución
- Clase
ContadorSeguro: Implementa los métodosincrementaryobtenerCuentade manera sincronizada. - Clase
HiloContador: ImplementaRunnabley en su métodorunincrementa el contador 1000 veces. - Clase
Main: Crea 10 hilos deHiloContador, los inicia y espera a que todos terminen antes de imprimir el valor final del contador.
Resumen
En esta sección, hemos aprendido sobre la sincronización en Java, incluyendo métodos sincronizados y bloques sincronizados. La sincronización es esencial para evitar condiciones de carrera y asegurar que los recursos compartidos sean accedidos de manera segura por múltiples hilos. Hemos visto ejemplos prácticos y realizado un ejercicio para reforzar estos conceptos. En la siguiente sección, exploraremos las utilidades de concurrencia en Java para manejar tareas más complejas de sincronización y coordinación entre hilos.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
Módulo 2: Flujo de Control
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
Módulo 4: Programación Orientada a Objetos Avanzada
Módulo 5: Estructuras de Datos y Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection
