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

  1. 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.
  2. Bloqueo (Lock): Un mecanismo que permite a un hilo obtener acceso exclusivo a un recurso compartido.
  3. Monitores: Cada objeto en Java puede actuar como un monitor, permitiendo que los métodos sincronizados controlen el acceso a los recursos compartidos.
  4. 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 clave synchronized 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 objeto lock 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:

  1. Crear una clase ContadorSeguro con un campo cuenta de tipo int.
  2. Implementar un método incrementar que incremente cuenta de manera segura utilizando la palabra clave synchronized.
  3. Implementar un método obtenerCuenta que devuelva el valor de cuenta de manera segura utilizando la palabra clave synchronized.
  4. Crear una clase HiloContador que implemente Runnable y en su método run incremente el contador 1000 veces.
  5. Crear una clase Main que inicie 10 hilos de HiloContador 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

  1. Clase ContadorSeguro: Implementa los métodos incrementar y obtenerCuenta de manera sincronizada.
  2. Clase HiloContador: Implementa Runnable y en su método run incrementa el contador 1000 veces.
  3. Clase Main: Crea 10 hilos de HiloContador, 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

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Programación Orientada a Objetos Avanzada

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

Módulo 11: Frameworks y Librerías de Java

Módulo 12: Construcción de Aplicaciones del Mundo Real

© Copyright 2024. Todos los derechos reservados