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
- Gestión de Threads: Creación, control, ciclo de vida
- Sincronización: synchronized, locks, semaphores
- Comunicación Inter-thread: wait/notify, blocking queues
- Colecciones Concurrentes: Estructuras de datos thread-safe
- Thread Pools: ExecutorService, ThreadPoolExecutor
- Thread Safety: Immutable, volatile, atomic classes
- Rendimiento: Granularidad de locks, reducción de contención
- 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
- Mutual Exclusion: Un recurso solo puede ser usado por un thread a la vez
- Hold and Wait: Un thread mantiene recursos mientras espera por otros
- No Preemption: Los recursos no pueden ser liberados forzosamente
- 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
-
¿Cuál es la diferencia entre wait() y sleep()? wait() libera el lock, sleep() lo mantiene. wait() requiere synchronized, sleep() no.
-
¡Explica qué es un deadlock y cómo evitarlo! Un deadlock es un bloqueo mutuo. Se evita mediante lock ordering y tryLock con timeout.
-
¿Cuándo usas volatile en lugar de synchronized? volatile para simple visibilidad de variables, synchronized para operaciones complejas.
-
¿Cuál es la diferencia entre Runnable y Callable? Runnable no retorna nada, Callable devuelve un resultado y puede lanzar excepciones.
Recursos principales
- https://docs.oracle.com/javase/tutorial/essential/concurrency/
- https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html
- https://www.baeldung.com/java-concurrency



