Skip to content
IRC-CodingIRC-Coding
Multithreading JavaThreads SincronizaciónWait NotifyConcurrent LocksSemaphoresThread Safety

Multithreading en Java: Threads y Sincronización

Domina multithreading en Java con threads, sincronización, wait/notify, concurrent locks y semaphores. Thread safety y optimización.

S

schutzgeist

16 min read
Multithreading en Java: Threads y Sincronización

Multithreading en Java: Threads, Sincronización, Wait/Notify y Concurrent Locks

Este artículo es una guía completa sobre Java Multithreading, que incluye threads, sincronización, wait/notify, concurrent locks y semaphores con ejemplos prácticos.

En Pocas Palabras

Multithreading permite ejecutar tareas en paralelo. La sincronización garantiza acceso consistente a los datos, mientras que los concurrent locks ofrecen mecanismos modernos para controlar threads.

Descripción Técnica Concisa

Multithreading es la capacidad de un programa para ejecutar múltiples threads simultáneamente. Cada thread tiene su propia pila de ejecución, pero comparte la memoria heap con otros threads.

Conceptos fundamentales:

Ciclo de vida del Thread

  • NEW: Thread creado pero aún no iniciado
  • RUNNABLE: Listo para ejecutarse (en ejecución o en espera de recursos)
  • BLOCKED: Esperando por un monitor-lock
  • WAITING: Esperando indefinidamente una condición
  • TIMED_WAITING: Esperando durante un tiempo limitado
  • TERMINATED: Thread finalizado

Mecanismos de Sincronización

  • synchronized: Sincronización basada en monitor
  • wait()/notify(): Comunicación entre threads
  • ReentrantLock: Mecanismo de lock flexible
  • ReadWriteLock: Locks separados para lectura y escritura
  • Semaphore: Control de acceso basado en contador
  • CountDownLatch: Esperar a múltiples threads
  • CyclicBarrier: Punto de sincronización para threads

Thread Safety

  • Immutable Objects: Thread-safe por naturaleza
  • Thread-Local: Datos específicos por thread
  • Volatile: Visibilidad de variables
  • Atomic Classes: Operaciones sin lock

Puntos Clave para Examinar

  • Threads: Procesos ligeros con su propia pila
  • Sincronización: Proteger recursos compartidos
  • Monitor: Mecanismo de sincronización basado en objetos
  • wait/notify: Comunicación entre threads
  • Deadlock: Bloqueo mutuo entre threads
  • Race Condition: Acceso descontrolado a datos compartidos
  • Volatile: Garantiza visibilidad entre threads
  • Relevancia Profesional: Importante para aplicaciones paralelas eficientes

Componentes Clave

  1. Gestión de Threads: Creación, control, ciclo de vida
  2. Sincronización: synchronized, locks, semaphores
  3. Comunicación Inter-thread: wait/notify, blocking queues
  4. Colecciones Concurrentes: Estructuras de datos thread-safe
  5. Thread Pools: ExecutorService, ThreadPoolExecutor
  6. Thread Safety: Immutable, volatile, atomic classes
  7. Rendimiento: Granularidad de locks, reducción de contención
  8. Debugging: Thread dumps, race conditions, deadlocks

Ejemplos Prácticos

1. Operaciones Básicas con Threads

public class ThreadGrundlagen {
    
    // Thread mediante herencia de Thread
    static class MeinThread extends Thread {
        private String name;
        
        public MeinThread(String name) {
            this.name = name;
        }
        
        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                System.out.println(name + " - Zählung: " + i);
                try {
                    Thread.sleep(500); // 500ms Pause
                } catch (InterruptedException e) {
                    System.out.println(name + " wurde unterbrochen");
                    return;
                }
            }
            System.out.println(name + " beendet");
        }
    }
    
    // Thread mediante implementación de Runnable
    static class MeinRunnable implements Runnable {
        private String name;
        
        public MeinRunnable(String name) {
            this.name = name;
        }
        
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                System.out.println(name + " - Arbeit: " + i);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    System.out.println(name + " unterbrochen");
                    return;
                }
            }
            System.out.println(name + " fertig");
        }
    }
    
    // Expresión lambda como Runnable
    static void lambdaThreadDemo() {
        Thread lambdaThread = new Thread(() -> {
            for (int i = 1; i <= 3; i++) {
                System.out.println("Lambda Thread - Schritt " + i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return;
                }
            }
        });
        
        lambdaThread.start();
    }
    
    public static void main(String[] args) {
        System.out.println("=== Thread-Grundlagen ===");
        
        // Thread mediante herencia
        MeinThread thread1 = new MeinThread("Thread-1");
        thread1.start();
        
        // Thread mediante Runnable
        Thread thread2 = new Thread(new MeinRunnable("Runnable-1"));
        thread2.start();
        
        // Lambda Thread
        lambdaThreadDemo();
        
        // Esperar a los threads
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Hauptthread beendet");
    }
}

2. Sincronización con synchronized

public class SynchronisationDemo {
    
    // Recurso compartido
    static class Zaehler {
        private int wert = 0;
        
        // Método sincronizado
        public synchronized void erhoehen() {
            wert++;
            System.out.println(Thread.currentThread().getName() + 
                             " erhöht auf: " + wert);
        }
        
        // Bloque sincronizado
        public void verringern() {
            synchronized(this) {
                wert--;
                System.out.println(Thread.currentThread().getName() + 
                                 " verringert auf: " + wert);
            }
        }
        
        public synchronized int getWert() {
            return wert;
        }
    }
    
    // Producer-Consumer con wait/notify
    static class Warenlager {
        private final int[] lager = new int[5];
        private int index = 0;
        
        public synchronized void einlagern(int ware) throws InterruptedException {
            // Esperar si almacén está lleno
            while (index >= lager.length) {
                System.out.println("Lager voll - Producer wartet");
                wait();
            }
            
            lager[index] = ware;
            index++;
            System.out.println(Thread.currentThread().getName() + 
                             " eingelagert: " + ware);
            
            // Notificar consumer
            notifyAll();
        }
        
        public synchronized int auslagern() throws InterruptedException {
            // Esperar si almacén está vacío
            while (index <= 0) {
                System.out.println("Lager leer - Consumer wartet");
                wait();
            }
            
            index--;
            int ware = lager[index];
            System.out.println(Thread.currentThread().getName() + 
                             " ausgelagert: " + ware);
            
            // Notificar producer
            notifyAll();
            
            return ware;
        }
    }
    
    static class Producer implements Runnable {
        private Warenlager lager;
        
        public Producer(Warenlager lager) {
            this.lager = lager;
        }
        
        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    lager.einlagern(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    static class Consumer implements Runnable {
        private Warenlager lager;
        
        public Consumer(Warenlager lager) {
            this.lager = lager;
        }
        
        @Override
        public void run() {
            try {
                for (int i = 1; i <= 10; i++) {
                    lager.auslagern();
                    Thread.sleep(150);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== Synchronisation Demo ===");
        
        // Sincronización simple
        Zaehler zaehler = new Zaehler();
        
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    zaehler.erhoehen();
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            });
            threads[i].setName("Thread-" + i);
        }
        
        // Iniciar threads
        for (Thread t : threads) {
            t.start();
        }
        
        // Esperar a los threads
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Endwert: " + zaehler.getWert());
        
        // Demo Producer-Consumer
        System.out.println("\n=== Producer-Consumer Demo ===");
        Warenlager lager = new Warenlager();
        
        Thread producer = new Thread(new Producer(lager), "Producer");
        Thread consumer = new Thread(new Consumer(lager), "Consumer");
        
        producer.start();
        consumer.start();
        
        try {
            producer.join();
            consumer.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3. Cerrojos concurrentes y sincronización moderna

import java.util.concurrent.locks.*;
import java.util.concurrent.*;

public class ConcurrentLocksDemo {
    
    // Ejemplo de ReentrantLock
    static class Bankkonto {
        private double kontostand;
        private final ReentrantLock lock = new ReentrantLock();
        
        public Bankkonto(double startbetrag) {
            this.kontostand = startbetrag;
        }
        
        public void einzahlen(double betrag) {
            lock.lock();
            try {
                double alterStand = kontostand;
                Thread.sleep(50); // Simuliere Verarbeitung
                kontostand = alterStand + betrag;
                System.out.println(Thread.currentThread().getName() + 
                                 " eingezahlt: " + betrag + 
                                 ", neuer Stand: " + kontostand);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        }
        
        public boolean abheben(double betrag) {
            lock.lock();
            try {
                if (kontostand >= betrag) {
                    double alterStand = kontostand;
                    Thread.sleep(50);
                    kontostand = alterStand - betrag;
                    System.out.println(Thread.currentThread().getName() + 
                                     " abgehoben: " + betrag + 
                                     ", neuer Stand: " + kontostand);
                    return true;
                } else {
                    System.out.println(Thread.currentThread().getName() + 
                                     " Konnte nicht abheben: " + betrag + 
                                     " (Stand: " + kontostand + ")");
                    return false;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } finally {
                lock.unlock();
            }
        }
        
        public double getKontostand() {
            lock.lock();
            try {
                return kontostand;
            } finally {
                lock.unlock();
            }
        }
    }
    
    // Ejemplo de ReadWriteLock
    static class ThreadSafeList {
        private final List<String> liste = new ArrayList<>();
        private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
        private final Lock readLock = rwLock.readLock();
        private final Lock writeLock = rwLock.writeLock();
        
        public void add(String element) {
            writeLock.lock();
            try {
                liste.add(element);
                System.out.println(Thread.currentThread().getName() + 
                                 " hinzugefügt: " + element);
            } finally {
                writeLock.unlock();
            }
        }
        
        public String get(int index) {
            readLock.lock();
            try {
                return liste.get(index);
            } finally {
                readLock.unlock();
            }
        }
        
        public List<String> getAll() {
            readLock.lock();
            try {
                return new ArrayList<>(liste); // Kopie zurückgeben
            } finally {
                readLock.unlock();
            }
        }
        
        public int size() {
            readLock.lock();
            try {
                return liste.size();
            } finally {
                readLock.unlock();
            }
        }
    }
    
    // Ejemplo de Semaphore
    static class RessourcenPool {
        private final Semaphore semaphore;
        private final List<String> ressourcen;
        
        public RessourcenPool(int maxRessourcen) {
            semaphore = new Semaphore(maxRessourcen);
            ressourcen = new ArrayList<>();
            for (int i = 1; i <= maxRessourcen; i++) {
                ressourcen.add("Ressource-" + i);
            }
        }
        
        public String acquire() throws InterruptedException {
            semaphore.acquire();
            
            synchronized(ressourcen) {
                if (!ressourcen.isEmpty()) {
                    String ressource = ressourcen.remove(0);
                    System.out.println(Thread.currentThread().getName() + 
                                     " acquired: " + ressource);
                    return ressource;
                }
            }
            
            semaphore.release();
            return null;
        }
        
        public void release(String ressource) {
            synchronized(ressourcen) {
                ressourcen.add(ressource);
                System.out.println(Thread.currentThread().getName() + 
                                 " released: " + ressource);
            }
            semaphore.release();
        }
    }
    
    // Ejemplo de CountDownLatch
    static class Worker implements Runnable {
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;
        private final int workerId;
        
        public Worker(CountDownLatch startSignal, CountDownLatch doneSignal, int workerId) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
            this.workerId = workerId;
        }
        
        @Override
        public void run() {
            try {
                // Warten auf Startsignal
                System.out.println("Worker " + workerId + " bereit");
                startSignal.await();
                
                // Arbeit ausführen
                System.out.println("Worker " + workerId + " arbeitet");
                Thread.sleep((long) (Math.random() * 1000));
                
                System.out.println("Worker " + workerId + " fertig");
                
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                doneSignal.countDown();
            }
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== Concurrent Locks Demo ===");
        
        // ReentrantLock Demo
        Bankkonto konto = new Bankkonto(1000.0);
        
        Thread[] bankThreads = new Thread[4];
        for (int i = 0; i < bankThreads.length; i++) {
            final int threadId = i;
            bankThreads[i] = new Thread(() -> {
                for (int j = 0; j < 3; j++) {
                    if (threadId % 2 == 0) {
                        konto.einzahlen(100);
                    } else {
                        konto.abheben(50);
                    }
                }
            }, "BankThread-" + i);
        }
        
        for (Thread t : bankThreads) {
            t.start();
        }
        
        for (Thread t : bankThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Endkontostand: " + konto.getKontostand());
        
        // ReadWriteLock Demo
        System.out.println("\n=== ReadWriteLock Demo ===");
        ThreadSafeList liste = new ThreadSafeList();
        
        // Writer Thread
        Thread writer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                liste.add("Element-" + i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    return;
                }
            }
        }, "Writer");
        
        // Reader Threads
        Thread[] readers = new Thread[3];
        for (int i = 0; i < readers.length; i++) {
            readers[i] = new Thread(() -> {
                for (int j = 0; j < 10; j++) {
                    List<String> alle = liste.getAll();
                    System.out.println(Thread.currentThread().getName() + 
                                     " gelesen: " + alle.size() + " Elemente");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }, "Reader-" + i);
        }
        
        writer.start();
        for (Thread reader : readers) {
            reader.start();
        }
        
        try {
            writer.join();
            for (Thread reader : readers) {
                reader.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // Semaphore Demo
        System.out.println("\n=== Semaphore Demo ===");
        RessourcenPool pool = new RessourcenPool(2);
        
        Thread[] poolThreads = new Thread[5];
        for (int i = 0; i < poolThreads.length; i++) {
            poolThreads[i] = new Thread(() -> {
                try {
                    String ressource = pool.acquire();
                    if (ressource != null) {
                        Thread.sleep(1000); // Ressource nutzen
                        pool.release(ressource);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "PoolThread-" + i);
        }
        
        for (Thread t : poolThreads) {
            t.start();
        }
        
        for (Thread t : poolThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // CountDownLatch Demo
        System.out.println("\n=== CountDownLatch Demo ===");
        int workerCount = 3;
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(workerCount);
        
        for (int i = 1; i <= workerCount; i++) {
            new Thread(new Worker(startSignal, doneSignal, i)).start();
        }
        
        try {
            Thread.sleep(1000);
            System.out.println("Alle Worker bereit - Startsignal!");
            startSignal.countDown();
            
            doneSignal.await();
            System.out.println("Alle Worker fertig!");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Aquí te presento las características principales de sincronización en Java. ReentrantLock proporciona más flexibilidad que synchronized, permitiendo intentos de bloqueo con tiempo límite y condiciones. ReadWriteLock optimiza escenarios con muchas lecturas y pocas escrituras, dejando que múltiples lectores accedan simultáneamente mientras bloquea durante escrituras.

Semaphore controla el acceso a un número limitado de recursos. En el ejemplo, limita a dos clientes concurrentes aunque cinco intentan acceder. CountDownLatch sincroniza múltiples threads en un punto de inicio común, útil para coordinación en pruebas o inicialización paralela.

Estas herramientas evitan deadlocks comunes del synchronized tradicional y ofrecen mayor control sobre el flujo de bloqueos.

4. Estructuras de datos thread-safe y clases Atomic

import java.util.concurrent.atomic.*;
import java.util.concurrent.*;

public class ThreadSafeCollections {
    
    // Atomic Classes Demo
    static class AtomicZaehler {
        private final AtomicInteger zaehler = new AtomicInteger(0);
        private final AtomicLong longZaehler = new AtomicLong(0);
        private final AtomicBoolean flag = new AtomicBoolean(false);
        private final AtomicReference<String> nachricht = new AtomicReference<>("");
        
        public void increment() {
            int alterWert = zaehler.getAndIncrement();
            System.out.println(Thread.currentThread().getName() + 
                             " increment: " + alterWert + " -> " + zaehler.get());
        }
        
        public void add(long wert) {
            long alterWert = longZaehler.getAndAdd(wert);
            System.out.println(Thread.currentThread().getName() + 
                             " add: " + alterWert + " + " + wert + " -> " + longZaehler.get());
        }
        
        public void toggleFlag() {
            boolean alterWert = flag.getAndSet(!flag.get());
            System.out.println(Thread.currentThread().getName() + 
                             " toggle: " + alterWert + " -> " + flag.get());
        }
        
        public void updateNachricht(String neueNachricht) {
            String alteNachricht = nachricht.getAndSet(neueNachricht);
            System.out.println(Thread.currentThread().getName() + 
                             " update: '" + alteNachricht + "' -> '" + neueNachricht + "'");
        }
        
        public int getZaehler() { return zaehler.get(); }
        public long getLongZaehler() { return longZaehler.get(); }
        public boolean getFlag() { return flag.get(); }
        public String getNachricht() { return nachricht.get(); }
    }
    
    // Concurrent Collections Demo
    static class ConcurrentCollectionsDemo {
        
        public static void hashMapDemo() {
            System.out.println("=== ConcurrentHashMap Demo ===");
            ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
            
            // Writer Threads
            Thread[] writers = new Thread[3];
            for (int i = 0; i < writers.length; i++) {
                final int threadId = i;
                writers[i] = new Thread(() -> {
                    for (int j = 0; j < 5; j++) {
                        String key = "Key-" + threadId + "-" + j;
                        map.put(key, threadId * 100 + j);
                        System.out.println(Thread.currentThread().getName() + 
                                         " put: " + key);
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }, "Writer-" + i);
            }
            
            // Reader Thread
            Thread reader = new Thread(() -> {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName() + 
                                     " size: " + map.size());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }, "Reader");
            
            // Iniciar todos los threads
            reader.start();
            for (Thread writer : writers) {
                writer.start();
            }
            
            // Esperar a que terminen
            try {
                for (Thread writer : writers) {
                    writer.join();
                }
                reader.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("Final map size: " + map.size());
        }
        
        public static void blockingQueueDemo() {
            System.out.println("\n=== BlockingQueue Demo ===");
            BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
            
            // Producer
            Thread producer = new Thread(() -> {
                try {
                    for (int i = 1; i <= 10; i++) {
                        String item = "Item-" + i;
                        queue.put(item);
                        System.out.println("Producer put: " + item + 
                                         " (queue size: " + queue.size() + ")");
                        Thread.sleep(200);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "Producer");
            
            // Consumer
            Thread consumer = new Thread(() -> {
                try {
                    for (int i = 1; i <= 10; i++) {
                        String item = queue.take();
                        System.out.println("Consumer take: " + item + 
                                         " (queue size: " + queue.size() + ")");
                        Thread.sleep(300);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "Consumer");
            
            producer.start();
            consumer.start();
            
            try {
                producer.join();
                consumer.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    // ThreadLocal Demo
    static class ThreadLocalDemo {
        private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 100);
        private static ThreadLocal<String> threadLocalName = new ThreadLocal<>();
        
        public static void demo() {
            System.out.println("=== ThreadLocal Demo ===");
            
            Thread[] threads = new Thread[3];
            for (int i = 0; i < threads.length; i++) {
                final int threadId = i;
                threads[i] = new Thread(() -> {
                    threadLocalName.set("Thread-" + threadId);
                    
                    for (int j = 0; j < 3; j++) {
                        int wert = threadLocalValue.get();
                        String name = threadLocalName.get();
                        
                        System.out.println(name + " wert: " + wert);
                        
                        // Cambiar el valor ThreadLocal
                        threadLocalValue.set(wert + threadId);
                        
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                    
                    // Limpiar ThreadLocal
                    threadLocalName.remove();
                    threadLocalValue.remove();
                }, "Thread-" + i);
            }
            
            for (Thread t : threads) {
                t.start();
            }
            
            for (Thread t : threads) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String[] args) {
        // Atomic Classes Demo
        System.out.println("=== Atomic Classes Demo ===");
        AtomicZaehler zaehler = new AtomicZaehler();
        
        Thread[] atomicThreads = new Thread[4];
        for (int i = 0; i < atomicThreads.length; i++) {
            final int threadId = i;
            atomicThreads[i] = new Thread(() -> {
                zaehler.increment();
                zaehler.add(threadId * 10);
                zaehler.toggleFlag();
                zaehler.updateNachricht("Nachricht von Thread-" + threadId);
            }, "AtomicThread-" + i);
        }
        
        for (Thread t : atomicThreads) {
            t.start();
        }
        
        for (Thread t : atomicThreads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Final values:");
        System.out.println("Zaehler: " + zaehler.getZaehler());
        System.out.println("LongZaehler: " + zaehler.getLongZaehler());
        System.out.println("Flag: " + zaehler.getFlag());
        System.out.println("Nachricht: " + zaehler.getNachricht());
        
        // Concurrent Collections Demo
        ConcurrentCollectionsDemo.hashMapDemo();
        ConcurrentCollectionsDemo.blockingQueueDemo();
        
        // ThreadLocal Demo
        ThreadLocalDemo.demo();
    }
}

Las clases Atomic de Java ofrecen operaciones atómicas sin sincronización explícita. En el ejemplo anterior vemos AtomicInteger, AtomicLong, AtomicBoolean y AtomicReference. Cada una proporciona métodos como getAndIncrement(), getAndSet() y compareAndSet() que garantizan atomicidad a nivel de hardware.

ConcurrentHashMap es una alternativa segura a HashMap cuando necesitas que múltiples threads accedan y modifiquen el mapa simultáneamente. En lugar de sincronizar toda la estructura, usa segmentación interna, lo que mejora significativamente el rendimiento en escenarios multi-thread.

BlockingQueue es ideal para patrones productor-consumidor. El productor agrega elementos con put(), que bloquea si la cola está llena, y el consumidor los retira con take(), que espera si la cola está vacía. Esta sincronización automática evita que tengas que gestionar manualmente los mecanismos de espera.

ThreadLocal proporciona variables aisladas por thread. Cada thread obtiene su propia instancia independiente, útil para contextos de solicitud, conexiones a bases de datos o cualquier estado que deba ser privado para cada thread. Recuerda siempre invocar remove() cuando termines, especialmente en aplicaciones que reutilizan threads como los thread pools, para evitar fugas de memoria.

Thread-Pools y ExecutorService

ThreadPoolExecutor

// Fixed Thread Pool
ExecutorService fixedPool = Executors.newFixedThreadPool(4);

// Cached Thread Pool
ExecutorService cachedPool = Executors.newCachedThreadPool();

// Single Thread Executor
ExecutorService singlePool = Executors.newSingleThreadExecutor();

// Scheduled Thread Pool
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);

// Ejecutar tareas
Future<String> future = fixedPool.submit(() -> {
    Thread.sleep(1000);
    return "Ergebnis";
});

// Tareas programadas
scheduledPool.scheduleAtFixedRate(() -> {
    System.out.println("Periodische Aufgabe");
}, 0, 1, TimeUnit.SECONDS);

// Apagar el pool
fixedPool.shutdown();
scheduledPool.shutdown();

Evitar Deadlocks

Condiciones para Deadlock

  1. Mutual Exclusion: Un recurso solo puede ser usado por un thread a la vez
  2. Hold and Wait: Un thread mantiene recursos mientras espera por otros
  3. No Preemption: Los recursos no pueden ser liberados forzosamente
  4. Circular Wait: Existe una secuencia circular de threads esperando recursos

Estrategias de prevención

// Lock Ordering - siempre lockear en el mismo orden
public void transfer(Account from, Account to, double amount) {
    // Sincroniza cuentas en orden consistente
    Account first = from.getId() < to.getId() ? from : to;
    Account second = from.getId() < to.getId() ? to : from;
    
    synchronized(first) {
        synchronized(second) {
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}

// TryLock con timeout
public boolean transferWithTryLock(Account from, Account to, double amount) {
    while (true) {
        try {
            if (from.getLock().tryLock(1, TimeUnit.SECONDS)) {
                try {
                    if (to.getLock().tryLock(1, TimeUnit.SECONDS)) {
                        try {
                            from.withdraw(amount);
                            to.deposit(amount);
                            return true;
                        } finally {
                            to.getLock().unlock();
                        }
                    }
                } finally {
                    from.getLock().unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
        // Breve pausa antes de reintentar
        Thread.sleep(100);
    }
}

Optimización de Performance

Granularidad de Locks

// Sincronización gruesa (no óptimo)
public synchronized void addElement(Object element) {
    // Toda la operación está bloqueada
    list.add(element);
    size++;
}

// Sincronización fina (mejor)
public void addElement(Object element) {
    synchronized(list) {
        list.add(element);
    }
    synchronized(this) {
        size++;
    }
}

Volatile vs Synchronized

// Volatile para simple visibilidad
private volatile boolean running = true;

public void stop() {
    running = false; // Visible para todos los threads
}

public void run() {
    while (running) {
        // Ejecutar trabajo
    }
}

// Synchronized para operaciones complejas
private int counter = 0;

public synchronized void increment() {
    counter++; // Operación atómica
}

Ventajas y Desventajas

Ventajas del Multithreading

  • Performance: Ejecución paralela en sistemas multi-core
  • Responsiveness: La UI sigue siendo reactiva durante operaciones largas
  • Utilización de recursos: Mejor aprovechamiento de recursos del sistema
  • Escalabilidad: Las tareas pueden distribuirse entre varios threads

Desventajas

  • Complejidad: La sincronización es propensa a errores
  • Debugging: Las race conditions son difíciles de reproducir
  • Overhead: La creación de threads y cambios de contexto consumen tiempo
  • Recursos: Mayor consumo de memoria y CPU

Preguntas frecuentes de examen

  1. ¿Cuál es la diferencia entre wait() y sleep()? wait() libera el lock, sleep() lo mantiene. wait() requiere synchronized, sleep() no.

  2. ¡Explica qué es un deadlock y cómo evitarlo! Un deadlock es un bloqueo mutuo. Se evita mediante lock ordering y tryLock con timeout.

  3. ¿Cuándo usas volatile en lugar de synchronized? volatile para simple visibilidad de variables, synchronized para operaciones complejas.

  4. ¿Cuál es la diferencia entre Runnable y Callable? Runnable no retorna nada, Callable devuelve un resultado y puede lanzar excepciones.

Recursos principales

  1. https://docs.oracle.com/javase/tutorial/essential/concurrency/
  2. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html
  3. https://www.baeldung.com/java-concurrency
Volver al blog
Share:

Entradas relacionadas