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 clavesynchronized
asegura 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 objetolock
se 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
ContadorSeguro
con un campocuenta
de tipoint
. - Implementar un método
incrementar
que incrementecuenta
de manera segura utilizando la palabra clavesynchronized
. - Implementar un método
obtenerCuenta
que devuelva el valor decuenta
de manera segura utilizando la palabra clavesynchronized
. - Crear una clase
HiloContador
que implementeRunnable
y en su métodorun
incremente el contador 1000 veces. - Crear una clase
Main
que inicie 10 hilos deHiloContador
y 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étodosincrementar
yobtenerCuenta
de manera sincronizada. - Clase
HiloContador
: ImplementaRunnable
y en su métodorun
incrementa 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