View Full Version : [Java] va bene questa implementazione?
tempo fa avevo scritto sul forum in merito alla stessa questione: realizzare in Java qualcosa che corrisponda praticamente a ciò che in Win32 è un evento auto-reset (risveglia i thread che lo attendono e si resetta automaticamente a non-signaled). PGI-Bis mi aveva dato una sua versione, che poi io ho perfezionato aggiungendo un paio di cose.
ora visto che io di wait e notifyAll non ci ho capito una beneamata, volevo appunto sapere se c'era qualche problema in quel paio di cose o se va ancora bene :D
ecco qua la mia implementazione:
public class Event
{
private volatile boolean wakeUp;
public Event()
{
wakeUp = false;
}
public synchronized void waitCondition() throws InterruptedException
{
wakeUp = false;
while (!wakeUp)
{
wait();
}
}
public synchronized void waitUninterruptibly()
{
while (true)
{
try
{
wait();
break;
}
catch (InterruptedException e)
{
}
}
}
public synchronized void notifyCondition()
{
wakeUp = true;
notifyAll();
}
}
grazie e buone feste :P
se ricordo bene la versione originaria di PGI-Bis faceva più o meno così:
public class Event
{
private volatile boolean wakeUp;
public Event()
{
wakeUp = false;
}
public synchronized void waitCondition() throws InterruptedException
{
while (!wakeUp)
{
wait();
}
}
public synchronized void notifyCondition()
{
wakeUp = true;
notifyAll();
}
}
ricordo bene che mancasse il reset del flag subito prima del while del metodo di attesa. da notare che in Win32 lo stato viene resettato immediatamente dopo il risveglio, mentre in questa mia classe solo subito prima di una vera e propria richiesta di attesa. è una differenza alla quale posso anche stare, anche se preferirei che il reset avvenisse subito. non l'ho messo subito perché ho pensato: "se metto a false wakeUp subito dopo la notifyAll creo una race condition: la waitCondition non fa manco in tempo ad accorgersi che il flag è stato messo a true e rientra nel while" ^_^'
c'è modo (semplice ^^) di risolvere questo problema?
Che succede se (A, B, C eccetera sono Thread):
A piglia il lock, invoca waitCondition, wakeup diventa false, A si addormenta e il lock viene liberato.
B piglia il lock.
C, che vuole invocare waitCondition, piglia il lock.
B, che ha il lock, invoca notifyCondition, wakeup diventa true, A si risveglia, b rilascia il lock al termine dell'invocazione.
A deve acquisire il lock prima di eseguire il metodo waitCondition dal punto successivo al wait() ma lo schedulatore gli preferisce C, anche lui in attesa del lock.
C piglia il lock, invoca waitCondition, wakeup diventa false, C si addormenta e rilascia il lock.
A finalmente trova la porta aperta, piglia il lock e TA DAH!, wakeup è false, quindi torna a dormire.
Azzerare l'evento fa sorgere problemi "esistenziali": e se quando lo azzero c'è ancora qualcuno che non si è stava dormendo da prima ma non ha ancora avuto modo di svegliarsi? Non essendo garantito l'ordine di risveglio, quello che ti trovi a dover accettare è che nessun principe venga mai a baciare una delle tue belle addormentate.
Quello che ritengo succeda in WIN32 è che l'azzerameno dell'evento causi la produzione di un nuovo lock contro il quale sono fatti "scontrare" tutti coloro che invochino waitCondition successivamente al notifyCondition. Dopodichè passa a notificare a tutti quelli che erano in attesa sul vecchio lock che è ora di svegliarsi.
Una cosa del genere:
public class Event {
private boolean wakeUp; //false per inizializzazione
public synchronized void waitCondition() throws InterruptedException {
while (!wakeUp) {
wait();
}
}
public synchronized void notifyCondition() {
wakeUp = true;
notifyAll();
}
}
public class ResettableEvent extends Event {
private volatile Event event = new Event();
public void waitCondition() throws InterruptedException {
event.waitCondition();
}
public void notifyCondition() {
Event cache = event;
event = new Event();
cache.notifyCondition();
}
}
Tutti quelli che al momento dell'invocazione di notifyCondition in ResettableEvent non abbiano ancora raggiunto la soglia di waitCondition si spiaccicheranno subito dopo nel metodo waitCondition.
Quello che ritengo succeda in WIN32 è che l'azzerameno dell'evento causi la produzione di un nuovo lock contro il quale sono fatti "scontrare" tutti coloro che invochino waitCondition successivamente al notifyCondition. Dopodichè passa a notificare a tutti quelli che erano in attesa sul vecchio lock che è ora di svegliarsi. sai che mi hai messo una pulce nell'orecchio? :D
http://www.reactos.org/generated/doxygen/db/dfe/ntoskrnl_2ke_2event_8c.html#a6
:D :D :D
però leggendo quel codice non noto blocchi se non quel KeAcquireDispatcherDatabaseLock. è difficile capirne il significato perché non so che cosa contenga questo "dispatcher database" (non credo sia roba dell'NT originale).
a meno che il "dispatcher database" non sia un concetto noto di teoria dei sistemi operativi che io nella mia ignoranza non conosco :Prrr:
comunque tornando alla classe Event, sostanzialmente le mie necessità ammontano all'usare un oggetto Event più volte tra due threads, uno che aspetta e un altro che notifica. ma in futuro il thread che aspetta potrebbe diventare al plurale -- N threads che aspettano. viceversa quello che notifica sempre uno rimane.
poiché lo stesso oggetto deve essere usato più volte (il thread che aspetta deve aspettare in loop e vorrei che non si risvegliasse due volte a causa della stessa notifica), da qualche parte un reset del flag mi è necessario. come dove e quando lo resetto? ^^
a dire il vero vorrei evitare di usare una seconda classe che usa ereditarietà ed incapsulamento insieme :|
per curiosità: se invece mettessi il reset subito dopo il while del metodo di attesa che cosa otterrei? otterrei che per svegliare N thread in attesa dovrei chiamare il metodo di notifica N volte, ho intuito bene?
public class Event
{
private volatile boolean wakeUp;
public Event()
{
wakeUp = false;
}
public synchronized void waitCondition() throws InterruptedException
{
while (!wakeUp)
{
wait();
}
wakeUp = false;
}
public synchronized void notifyCondition()
{
wakeUp = true;
notifyAll();
}
}
per curiosità: se invece mettessi il reset subito dopo il while del metodo di attesa che cosa otterrei? otterrei che per svegliare N thread in attesa dovrei chiamare il metodo di notifica N volte, ho intuito bene? o che potrebbe essere necessario chiamare il metodo di notifica potenzialmente fino a N volte?
a dire il vero vorrei evitare di usare una seconda classe che usa ereditarietà ed incapsulamento insieme :| mi sa che è l'unica... :mbe:
Alla fin fine non so neanche perchè io abbia reso ResettableEvent estensione di Event. Quando uno vive di certezze :D.
Se non ti interessa la relazione tra i due, non vedo problemi nel separarli (cioè ResettableEvent NON extends Event).
ho scritto un'altra implementazione che con un solo oggetto Event dovrebbe risolvere il problema che hai descritto al post #3. però fa uso di un ReentrantReadWriteLock :D
public class Event
{
private volatile boolean wakeUp;
private ReentrantReadWriteLock notificationLock;
public Event()
{
wakeUp = false;
notificationLock = new ReentrantReadWriteLock();
}
public synchronized void waitCondition() throws InterruptedException
{
notificationLock.readLock().lock();
do
{
wait();
}
while (!wakeUp);
notificationLock.readLock().unlock();
}
public synchronized void waitUninterruptibly()
{
while (true)
{
try
{
wait();
break;
}
catch (InterruptedException e)
{
}
}
}
public synchronized void notifyCondition()
{
wakeUp = true;
notifyAll();
reset();
}
private void reset()
{
notificationLock.writeLock().lock();
wakeUp = false;
notificationLock.writeLock().unlock();
}
}
1) questa implementazione risolve il problema?
2) chiaramente stavo pensando a qualcosa di più semplice, e chiaramente qualcosa di più semplice esiste visto che tu l'hai realizzata con un Event al posto di un ReentrantReadWriteLock :D perciò mi chiedo: cosa, nell'implementazione col ReentrantReadWriteLock, è superfluo?
LOOOOOOOL, ma scusa ora che ci penso :D :D :D
public class Lol
{
private ReentrantReadWriteLock notificationLock;
public Lol()
{
notificationLock = new ReentrantReadWriteLock();
notificationLock.writeLock().lock();
}
public void waitCondition()
{
notificationLock.readLock().lock();
notificationLock.readLock().unlock();
}
public void notifyCondition()
{
notificationLock.writeLock().unlock();
notificationLock.writeLock().lock();
}
}
MHUWAMHWAUWMAHWUAMWAHUWA
:sbonk: :sbonk: :sbonk:
adesso per curiosità voglio andare a leggermi il codice di ReentrantReadWriteLock :D :D :D
adesso per curiosità voglio andare a leggermi il codice di ReentrantReadWriteLock :D :D :D madonna che casino... :huh:
mi fido va' :D :D :D
PGI-Bis, che ne pensi? quest'ultima lamerata col ReentrantReadWriteLock ha problemi? ^^
Non risolve il problema della contesa. Potrebbe tuttavia se dichiarassi il lock "fair" (il costruttore che accetta un boolean). In quel caso la contesa per il lock favorisce il Thread che attende da più tempo. Non ti serve, tuttavia, un ReadWriteLock per fare questo, ti basta un ReentrantLock.
Non risolve il problema della contesa. cioè semplicemente il fatto che i thread in attesa non vengano sbloccati in ordine...? a me basta che vengano sbloccati tutti... ^^
Non ti serve, tuttavia, un ReadWriteLock per fare questo, ti basta un ReentrantLock. uno solo? meglio ancora!! e come dovrei fare? ^^
cos'ha di superfluo il ReentrantReadWriteLock?
grazie per la pazienza :D
Perchè siano sbloccati tutti quelli in attesa quello che ci serve è che prima del reset tutti i thread bloccati abbiano l'occasione di eseguire il codice che determina il proseguimento del loro lavoro.
Normalmente non c'è un ordine nella scelta tra chi sarà il prossimo ad acquisire il lock (tanto che si tratti di un java.util.concurrent.Lock quanto di un blocco synchronized).
Abbiamo 50 thread che si sono bloccati e che per potersi svegliare devono prima acquisire il lock. Il 51° Thread arriva e libera tutti. Il reset opera dicendo al 51° Thread che deve riacquisire il lock. Benchè il 51° sia l'ultimo arrivato e ci siano altri 50 in coda prima di lui nulla vieta che sia proprio il 51° ad essere il primo ad acquisire il lock (meglio sarebbe parlare di mutex).
Ma se il lock è "fair", cioè se garantisce che chi attende da più tempo acquisisca per primo il lock quando questo sia disponibile, allora tra il:
notificationLock.unlock();
e
notificationLock.lock();
del "risveglio", tutti i 50 Thread in coda avranno l'opportunità di acquisire e rilasciare il mutex conteso.
ReadWriteLock usa due mutex per consentire delle ottimizzazioni (di cui non ho assolutamente idea) nella gestione della contesa tra due classi di Thread: una che vuole leggere qualcosa e una che cambia il valore di quello che gli altri leggono. Suppone inoltre che la classe dei lettori abbia un numero più elevato di componenti rispetto a quella degli scrittori (idealmente uno solo).
Puoi certamente avvantaggiarti di questa caratteristica ma non è strettamente necessaria al fine di interrompere l'esecuzione di un manipolo di Thread finchè qualcun'altro non decida che è ora di svegliarsi.
Il true nel costruttore di LOCK è quello che stabilisce il rispetto dell'ordine temporale di acquisizione del mutex. Dal punto di vista delle librerie. Poi c'è il problema delle politiche di scheduling del sistema operativo. Non è detto che l'essere arrivati prima significhi per ciò stesso che il Thread sarà effettivamente svegliato dallo schedulatore di sistema. Ritengo tuttavia che sia un caso limite.
import java.util.concurrent.locks.*;
public class Event {
private final ReentrantLock LOCK = new ReentrantLock(true);
public Event() {
LOCK.lock();
}
public void waitCondition() {
LOCK.lock();
LOCK.unlock();
}
public void notifyCondition() {
LOCK.unlock();
LOCK.lock();
}
}
Non so Doug Lea avesse esattamente in mente questo tipo di uso dei lock quando ne ha diretto la creazione. Magari dopo do un'occhiata al suo libro e vedo se c'è qualche inghippo. A me non sembra.
Il true nel costruttore di LOCK è quello che stabilisce il rispetto dell'ordine temporale di acquisizione del mutex. Dal punto di vista delle librerie. Poi c'è il problema delle politiche di scheduling del sistema operativo. Non è detto che l'essere arrivati prima significhi per ciò stesso che il Thread sarà effettivamente svegliato dallo schedulatore di sistema. è proprio per questo che non mi andrebbe molto di affidarmi alla politica di fairness: perché non è detto che funziona :D
piuttosto devo ancora capire bene qual è il problema usando il ReadWriteLock... :mbe:
ora me lo rileggo un po' di volte... :mbe:
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.