Java wait() spiegato: come funziona, come usarlo in sicurezza e differenze rispetto a sleep()

目次

1. Cos’è Java wait()? (Comprendi l’idea di base rapidamente)

java wait si riferisce a un metodo usato per mettere temporaneamente in pausa un thread (un flusso di esecuzione) e attendere una notifica da un altro thread.
Viene usato nel multithreading (esecuzione simultanea di più attività) per realizzare un controllo coordinato tra i thread.

Il punto chiave è che wait() non è “solo attendere che il tempo passi”.
Finché un altro thread non chiama notify() o notifyAll(), il thread target rimane nello stato di attesa (WAITING).

1.1 Il significato di base di wait()

L’essenza di wait() si riduce a questi tre punti:

  • Mettere il thread corrente in uno stato di attesa
  • Rilasciare temporaneamente il lock dell’oggetto target
  • Riprendere l’esecuzione quando viene notificato da un altro thread

Flusso concettuale:

  1. Entrare in un blocco synchronized
  2. Chiamare wait()
  3. Rilasciare il lock e passare allo stato di attesa
  4. Un altro thread chiama notify()
  5. Riacquisire il lock, quindi riprendere l’esecuzione

Esempio di sintassi semplice:

synchronized(obj) {
    obj.wait();
}

⚠ Nota

  • wait() non è “pausa per un tempo fisso”, ma “attesa di una notifica”.
  • Se nessuno notifica, può attendere indefinitamente.

1.2 wait() è un metodo di Object

wait() non è un metodo di Thread.
È un metodo di Object.

Definizione (concetto):

public final void wait()

In altre parole:

  • Ogni oggetto Java ha wait()
  • Si attende in base all’oggetto che si sta monitorando

Falsi miti comuni:

❌ Pensare che sia Thread.wait()
❌ Pensare che “fermi direttamente un thread”

Comprensione corretta:

✔ “Attendere sul monitor (lock) di un oggetto”

*Monitor = il lock gestito da synchronized.

1.3 Requisito obbligatorio per usare wait()

Per usare wait(), devi chiamarlo all’interno di un blocco synchronized.

Perché:

  • wait() rilascia il lock che possiedi attualmente
  • Se non possiedi il lock, la coerenza non può essere garantita

Esempio errato:

Object obj = new Object();
obj.wait();   // ❌ IllegalMonitorStateException

Esempio corretto:

Object obj = new Object();

synchronized(obj) {
    obj.wait();
}

Eccezione lanciata:

IllegalMonitorStateException

Questo significa “wait() è stato chiamato senza possedere il lock”.

⚠ Dove i principianti spesso si bloccano

  • Confonderlo con sleep() (sleep non rilascia il lock)
  • Dimenticare di scrivere synchronized
  • Non chiamare mai notify, quindi il thread resta in attesa per sempre
  • Chiamare notify su un oggetto diverso

✔ Riepilogo più importante finora

  • wait() è un metodo di Object
  • Può essere usato solo all’interno di synchronized
  • Rilascia il lock e attende una notifica
  • Non è un semplice “ritardo temporale”

2. Uso corretto di wait() (Comprendi con esempi di codice)

wait() non ha senso da solo.
Funziona solo se combinato con notify / notifyAll.

In questo capitolo lo spiegheremo passo passo, dal setup minimo ai pattern pratici.

2.1 Sintassi di base

La struttura corretta più piccola è la seguente:

Object lock = new Object();

synchronized(lock) {
    lock.wait();
}

Tuttavia, così com’è si bloccherà per sempre.
Motivo: non c’è alcuna notifica (notify).

2.2 Esempio con notify (Il più importante)

wait() significa “attendere una notifica”.
Pertanto, un altro thread deve chiamare notify.

✔ Esempio minimo funzionante

class Example {
    private static final Object lock = new Object();

    public static void main(String[] args) {

        Thread waitingThread = new Thread(() -> {
            synchronized(lock) {
                try {
                    System.out.println("Waiting...");
                    lock.wait();
                    System.out.println("Resumed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread notifyingThread = new Thread(() -> {
            synchronized(lock) {
                System.out.println("Notifying...");
                lock.notify();
            }
        });

        waitingThread.start();
        notifyingThread.start();
    }
}

Execution Flow

  1. waitingThread acquires lock
  2. It calls wait() , releases the lock, and waits
  3. notifyingThread acquires lock
  4. It calls notify() to signal the waiting thread
  5. After re-acquiring the lock, execution resumes

2.3 Difference Between notify and notifyAll

MethodBehavior
notify()Resumes only one waiting thread
notifyAll()Resumes all waiting threads

In real-world code, notifyAll() is often considered safer.

Why:

  • With notify() , a thread that should not resume might be selected
  • When multiple conditions exist, inconsistencies become more likely

2.4 The Correct Practical Pattern (Use while)

wait() is typically used together with a condition check.

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
}

Why:

  • Spurious wakeups (resuming without notification) can happen
  • After notifyAll , the condition may still not be satisfied

❌ Incorrect example:

if (!condition) {
    lock.wait();
}

This is not safe.

⚠ Common Mistakes

  • Calling wait and notify on different objects
  • Assuming it resumes immediately after notify (it must re-acquire the lock)
  • Calling notify outside synchronized
  • Forgetting to update the condition variable

✔ Implementation Steps Summary

  1. Create a shared lock object
  2. Wrap with synchronized
  3. Check the condition with while
  4. Wait with wait()
  5. Notify from another thread using notify / notifyAll
  6. Always update the condition

3. The Difference Between wait and sleep (The Core Search Intent)

Many users who search for “java wait” want to understand how it differs from sleep().
They may look similar, but their design philosophy and use cases are completely different.

3.1 The Decisive Differences (Comparison Table)

Itemwaitsleep
Defined inObjectThread
Releases lockYesNo
Where to useMust be inside synchronizedCan be used anywhere
PurposeInter-thread communicationSimple time delay

3.2 The Biggest Difference: “Does It Release the Lock?”

✔ How wait Behaves

  • Releases the lock currently held
  • Allows other threads to acquire the lock
  • Resumes via notify
    synchronized(lock) {
        lock.wait();
    }
    

✔ How sleep Behaves

  • Stops while still holding the lock
  • Other threads cannot acquire the lock
    Thread.sleep(1000);
    

⚠ This is the most important point
sleep = “wait for time”
wait = “wait for a notification”

3.3 How to Choose in Real-World Work

✔ When to Use wait

  • Producer/Consumer patterns
  • Waiting for data to arrive
  • Waiting for a state change
  • Coordinated control between threads

✔ When to Use sleep

  • Simple delay processing
  • Adjusting retry intervals
  • Waiting in test code

3.4 Using wait with a Timeout

wait can also be called with a timeout.

lock.wait(1000);  // Attendi fino a 1 secondo

But note:

  • It may return after the timeout, but there is no guarantee the condition is satisfied
  • You must re-check the condition using while

⚠ Points That Confuse Beginners

  • Thinking sleep resumes via notification → ❌
  • Thinking wait always resumes after time → ❌
  • Thinking locks are released during sleep → ❌

✔ Conclusion

  • sleep is just a time delay
  • wait is a mechanism for inter-thread communication
  • The biggest difference is whether the lock is released

4. Advanced wait Knowledge (Must-Know for Safe Usage)

wait looks simple, but misuse can easily cause bugs.
Here we’ll explain the essential knowledge needed to implement it safely.

4.1 wait with a Timeout

wait has a timed version.

synchronized(lock) {
    lock.wait(1000);  // Attendi fino a 1 secondo
}

Behavior:

  • Waits up to the specified time
  • Returns immediately if notified
  • May also return when the time expires

⚠ Importante
Anche se ritorna a causa di un timeout, non significa che “la condizione sia vera”.

Combina sempre con un controllo della condizione.

4.2 Che cos’è una sveglia spuriosa (Spurious Wakeup)?

Una sveglia spuriosa (Spurious Wakeup) è un fenomeno in cui
wait può ritornare anche senza una notifica.

La specifica Java consente che ciò accada.

Ciò significa:

  • Nessuno ha chiamato notify
  • La condizione non è cambiata
  • Eppure può comunque ritornare

Ecco perché è obbligatorio usare while invece di if.

4.3 Il pattern corretto (avvolgere con while)

Esempio di implementazione corretta:

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
    // Code after the condition becomes true
}

Perché:

  • Protezione contro le sveglie spuriose
  • La condizione può ancora essere falsa dopo notifyAll
  • Sicurezza quando più thread sono in attesa

❌ Esempio pericoloso:

synchronized(lock) {
    if (!condition) {
        lock.wait();  // Dangerous
    }
}

Questo stile può portare a comportamenti errati.

4.4 Relazione con i deadlock

wait rilascia un lock, ma
i deadlock possono comunque verificarsi quando sono coinvolti più lock.

Esempio:

  • Thread A → lock1 → lock2
  • Thread B → lock2 → lock1

Mitigazioni:

  • Utilizzare un ordine di acquisizione dei lock coerente
  • Ridurre al minimo il numero di lock

⚠ Errori critici comuni

  • Non aggiornare la variabile di condizione
  • Chiamare notify senza modificare lo stato
  • Annidare synchronized troppo spesso
  • Non mantenere l’ordine dei lock coerente tra più lock

✔ Riepilogo avanzato

  • Avvolgere wait con while è una regola assoluta
  • Anche con timeout, è necessario ricontrollare la condizione
  • Un design dei lock scadente può causare deadlock

5. Errori comuni e come risolverli

wait è un’API di basso livello, quindi gli errori di implementazione causano direttamente bug.
Qui riassumeremo gli errori più comuni, le loro cause e come risolverli.

5.1 IllegalMonitorStateException

Questo è l’errore più comune.

Quando si verifica:

  • Chiamare wait al di fuori di un blocco synchronized
  • Chiamare notify al di fuori di un blocco synchronized

❌ Esempio errato

Object lock = new Object();
lock.wait();  // Exception thrown

✔ Esempio corretto

synchronized(lock) {
    lock.wait();
}

Riepilogo della correzione:

  • Chiamare sempre wait / notify all’interno di synchronized sullo stesso oggetto
  • Utilizzare un unico oggetto lock coerente

5.2 Non riprende nemmeno dopo notify

Cause comuni:

  • Chiamare notify su un oggetto diverso
  • La variabile di condizione non viene aggiornata
  • Tenere il lock troppo a lungo dopo notify

❌ Errore tipico

synchronized(lock1) {
    lock1.wait();
}

synchronized(lock2) {
    lock2.notify();  // Different object
}

✔ Usa sempre lo stesso oggetto

synchronized(lock) {
    lock.wait();
}

synchronized(lock) {
    condition = true;
    lock.notifyAll();
}

5.3 wait non termina mai

Cause principali:

  • notify non viene mai chiamato
  • Logica di controllo della condizione errata
  • La condizione del while rimane vera per sempre

Passaggi di debug:

  1. Verifica che notify sia effettivamente chiamato
  2. Verifica che la variabile di condizione venga aggiornata
  3. Usa log per tracciare l’ordine di esecuzione dei thread

5.4 Deadlock

Pattern tipico:

synchronized(lock1) {
    synchronized(lock2) {
        ...
    }
}

Se un altro thread acquisisce i lock in ordine opposto, il programma può bloccarsi.

Mitigazioni:

  • Utilizzare un ordine di acquisizione dei lock coerente
  • Usare un unico lock, se possibile
  • Considerare l’uso di java.util.concurrent

5.5 Gestione di InterruptedException

wait lancia InterruptedException.

try {
    lock.wait();
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

Gestione consigliata:

  • Ripristinare il flag di interruzione (re-interrupt)
  • Non inghiottire l’eccezione

⚠ Errori molto comuni nella pratica

  • Avvolgere wait con if
  • Chiamare notify prima di aggiornare la condizione
  • Gestire più lock in modo ambiguo
  • Usare un blocco catch vuoto

✔ Regole fondamentali per evitare errori

  • synchronized è obbligatorio
  • Usa lo stesso oggetto lock in modo coerente
  • Usa sempre il pattern while
  • Aggiorna lo stato prima di chiamare notify
  • Gestisci correttamente le interruzioni

6. Alternative moderne in Java (Prospettiva pratica)

wait / notify sono strumenti di coordinamento dei thread a basso livello esistenti fin dai primi Java.
Nello sviluppo reale moderno, è comune utilizzare API più sicure e di livello superiore invece.

In questo capitolo, riassumeremo perché le alternative sono consigliate.

6.1 wait/notify sono API a basso livello

Quando usi wait, devi gestire manualmente tutto da solo.

Cose che devi gestire:

  • Controllo del lock
  • Variabili di condizione
  • Ordine di notifica
  • Protezione contro risvegli spurii
  • Evitare deadlock
  • Gestione delle interruzioni

In altre parole, è un design che facilmente diventa un terreno fertile per bug.

Diventa drammaticamente più complesso quando sono coinvolte più condizioni e più thread.

6.2 Classi alternative comuni

Dal Java 5, è disponibile il pacchetto java.util.concurrent.

BlockingQueue

Per passare dati tra thread.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();

queue.put("data");   // Waiting is handled automatically
queue.take();        // Waits until data arrives

Caratteristiche:

  • Non è necessario wait/notify
  • Meno probabile causare deadlock
  • Molto comunemente usato nei sistemi reali

CountDownLatch

Attende che più thread completino.

CountDownLatch latch = new CountDownLatch(1);

latch.await();   // Wait
latch.countDown();  // Signal

Casi d’uso:

  • Attendere il completamento dell’inizializzazione
  • Sincronizzare attività parallele

ReentrantLock + Condition

Un’API che fornisce un meccanismo simile a wait, ma più esplicito.

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();
try {
    condition.await();
} finally {
    lock.unlock();
}

Benefici:

  • Può gestire più condizioni
  • Controllo del lock più flessibile

6.3 Cosa dovresti usare in pratica?

Raccomandazioni per scenario:

GoalRecommended Tool
Passing dataBlockingQueue
Simple synchronizationCountDownLatch
Complex condition controlReentrantLock + Condition
LearningUnderstanding wait is essential

Conclusione:

  • È importante comprendere wait durante l’apprendimento
  • In pratica, preferire il pacchetto concurrent

⚠ Criteri pratici di decisione

  • Hai davvero bisogno di implementare tu stesso il coordinamento dei thread?
  • La libreria standard può risolverlo invece?
  • Puoi garantire leggibilità e manutenibilità?

In molti casi, puoi risolvere il problema senza usare wait.

✔ Riepilogo del capitolo

  • wait è un’API a basso livello
  • Il pacchetto concurrent è lo standard moderno
  • Prioritizza la sicurezza nel codice reale

7. Riepilogo (Note finali per principianti)

java wait è un meccanismo usato per attendere cambiamenti di stato tra thread.
Il punto più importante è che non è un semplice ritardo temporale, ma un “meccanismo di controllo basato su notifiche”.

Riassumiamo quanto abbiamo coperto dal punto di vista dell’implementazione.

7.1 L’essenza di wait

  • Un metodo di Object
  • Può essere usato solo all’interno di synchronized
  • Rilascia il lock e attende quando chiamato
  • Riprende tramite notify / notifyAll

7.2 Tre regole per scrivere codice sicuro

1) Usa sempre synchronized
2) Avvolgi con while
3) Aggiorna lo stato prima di chiamare notify

Modello corretto:

synchronized(lock) {
    while (!condition) {
        lock.wait();
    }
}

Lato notifier:

synchronized(lock) {
    condition = true;
    lock.notifyAll();
}

7.3 La differenza decisiva rispetto a sleep

Itemwaitsleep
Releases lockYesNo
Use caseInter-thread communicationTime delay

È importante non confonderli.

7.4 Dove si colloca nello sviluppo reale

  • Comprendere è essenziale
  • Ma nella pratica, dare priorità al pacchetto concurrent
  • Progettare un controllo personalizzato basato su wait con molta attenzione

7.5 Checklist finale

  • Stai usando lo stesso oggetto lock?
  • Lo stai chiamando all’interno di synchronized?
  • È avvolto da un while?
  • Aggiorni lo stato prima di notify?
  • Gestisci correttamente le interruzioni?

Se segui questi consigli, puoi ridurre significativamente il rischio di bug gravi.

FAQ

Q1. wait è un metodo della classe Thread?

No. È un metodo della classe Object.

Q2. Posso usare wait senza synchronized?

No. Lancerà IllegalMonitorStateException.

Q3. Quale dovrei usare: notify o notifyAll?

Se dai priorità alla sicurezza, si consiglia notifyAll.

Q4. wait può essere usato con un timeout?

Sì. Usa wait(long millis).

Q5. Perché usare while invece di if?

Per proteggersi da risvegli spurii e dal ritorno prima che la condizione sia soddisfatta.

Q6. wait è comunemente usato nel codice di produzione moderno?

Negli ultimi anni, l’uso di java.util.concurrent è diventato l’approccio dominante.

Q7. Posso sostituirlo con sleep?

No. sleep non rilascia il lock.