Capito, quindi quando faccio l'acquire() in realtà il Thread va in busy waiting, non va in wait.
Quindi dovrei fare wait() per mettere in pausa un Thread?
Nel tuo leggi() postato prima, c'è come ho detto in precedenza una "precondizione":
la lista non deve essere vuota
Se è vuota, nel tuo codice hai fatto il release e poi un
return ma c'è una questione: dovrebbe quindi essere il chiamante a ripetere il leggi() ma il metodo ha tipo di ritorno void, pertanto il chiamante non ha alcun appiglio per capire se quel leggi() ha "letto" qualcosa o se invece no. Quindi fatto così ha poco senso. Avrebbe invece più senso se il leggi() fosse "bloccante", ovvero sta lì in attesa che la lista diventi non-vuota.
Problema: se si usa un classico "semaforo" si deve per forza usare un meccanismo di attesa "attiva". Il test per empty deve essere fatto tenendo il permit del semaforo (così come durante il resto della lettura se non-vuota). Ma se è vuota, devi rilasciare il permit affinché un scrivi() possa inserire qualcosa. E quindi dovresti poi riacquisire il permit di nuovo, ecc..
Quindi come ho già detto, servirebbe un ciclo nel leggi(). Ciclo però che
consuma CPU perché se nessuno sta scrivendo (quindi nessuno cerca di fregare il permit), il leggi è continuamente in loop tra acquire e release, che sono praticamente immediati. Insomma, il thread NON va mai in sospensione (non per questi acquire/release di per sé ... il sistema però potrebbe togliergli cpu per altri motivi).
Morale: per fare un leggi "bloccante" come si deve, un semaforo non è proprio la cosa migliore. Si deve usare quella che si chiama una "condition queue".
E si può quindi usare la condition queue intrinseca degli oggetti (i metodi wait/notify/notifyAll di un oggetto) oppure quella dei lock "espliciti" presenti nel package java.util.concurrent.locks .
Il wait() DEVE essere invocato su un oggetto di cui il thread ha acquisito il "monitor" (il lock, per dirlo semplicemente). Quindi deve stare in un contesto synchronized. Il wait() nota bene, fa andare il thread in sospensione ma RILASCIA il lock! Questo è il punto essenziale.
E il wait va sempre fatto in un piccolo ciclo che deve testare la condizione che deve "reggere" affinché il thread resti in sospensione. Ci sono motivazioni che ora non sto a precisare.
Se si pensa ad una classica "coda" ma bloccante (in cui il put è illimitato mentre il get è bloccante se vuota), la condition queue è appropriata in questo caso. Il get() dovrebbe essere qualcosa del genere (abbozzato):
Codice:
public class CodaBloccante<E> {
// .....altro.....
public synchronized E get() throws InterruptedException {
while (vuota) {
wait();
}
// estrai elemento
// return elemento
}
}
È abbozzato/incompleto ma serve a dare l'idea. Quel "vuota" l'ho scritto come variabile ma potrebbe essere la invocazione di un metodo apposito. Non ha importanza. Il concetto è: "finché vuota STAI in wait".
Un put() (idem synchronized) inserirà un elemento nella struttura dati della coda (non mostrata sopra) e poi farà un notify() o meglio un notifyAll() in modo che uno/più thread in attesa
su quella condition queue (di quell'oggetto) si "svegli" e ritenti il get.
Piu chiaro?