[Java], concurrency wait/notify

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Op dit moment ben ik bezig met concurrency in Java. Met het wait/notify mechanisme heb ik nog wat moeite. Bijvoorbeeld:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class WaitingNotify1 {
    public static void main(String [] args) {
        ExecutorService executer=Executors.newCachedThreadPool();
        WaitingThread waitingThread=new WaitingThread();
        executer.execute(waitingThread);
        executer.execute(new NotifyThread(waitingThread));
    }
}

class WaitingThread implements Runnable {
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println("Call wait()");
                synchronized (this) {
                    wait();
                }
                System.out.println("wait() called");
            }
            System.out.println("Waiting finished");
            
        } catch (InterruptedException e) {  }
    }
}

class NotifyThread implements Runnable {
    private final WaitingThread waitingThread;
    
    public NotifyThread(WaitingThread waitingThread) {
        this.waitingThread=waitingThread;
    }
    
    @Override
    public void run() {
        try {
            synchronized (waitingThread) {
                System.out.println("Sleep 1 second");
                TimeUnit.SECONDS.sleep(1);
                System.out.println("Call notifyAll()");
                waitingThread.notifyAll();
                System.out.println("notifyAll() called");
            }
        } catch (InterruptedException e) {  }
    }
}
Output:
Call wait()
Sleep 1 second
Call notifyAll()
notifyAll() called
wait() called
Call wait()


Probleem is dat niet uit de waiting loop wordt gesprongen. Na de call naar notify(), zou de thread interrupted moeten zijn en dus uit de loop moeten springen en de melding moeten geven dat het wachten voorbij is. Ipv daarvan wordt wait() nogmaals aangeroepen en blijft de thread eeuwig wachten. Hoe kan ik zorgen dat de loop wordt beeindigd als notify() wordt aangeroepen?

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
Waarom zou de thread opeens interrupted zijn?

Als je wil dat de thread exit nadat wait() klaar is dan kun je de hele while-lus achterwege laten. Wil je de thread afbreken waar 'ie ook mee bezig is, dan kun je Thread.interrupt() aanroepen, maar dan moet je de thread-code wel zo geschreven hebben dat 'ie op elk synchronisatiepunt afgebroken kan worden.

offtopic:
Gebruik liever [code=java] in plaats van [code]-tags voor het posten van Java code; dan krijg je syntax highlighting cadeau.

[ Voor 46% gewijzigd door Soultaker op 27-02-2011 17:39 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Als ik de while lus weghaal werkt het, logisch!
Maar in het boek waar ik in bezig ben, staat dat wait() altijd in een while lus moet staan :? (ik zal het stuk nog ff doorlezen, wat ze nou precies bedoelen)

Het gaat er dus om, om uit de while te kunnen springen, na een notify (zonder dat een flag). In het boek wordt wel gebruik gemaakt van Thread.interrupted().

Ik vermoed dat het probleem is (waarom interrupted() nioet werkt) dat de thread waar de while lus in staat de current thread is en niet de thread waar notify wordt aangeroepen.

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
Object.wait()/Object.notify() en Thread.interrupt() zijn twee verschillende methoden om te signaleren tussen threads. interrupt wordt meestal gebruikt om een thread af te sluiten; wait/notify worden gebruikt voor communicatie tijdens de executie (bijvoorbeeld om in een consumer/producer te signaleren dat er een nieuw item te verwerken is).

Praktisch gezien is het verschil natuurlijk dat interrupt op een specifieke thread werkt, en notify niet. In de meeste gevallen is gebruik van wait/notify de aan te raden methode. De reden dat wait meestal binnen een lus gebruikt wordt is dat er spurious wakeups kunnen plaatsvinden: wait() kan returnen zonder dat er een corresponderende call naar notify() of notifyAll() geweest is. Daarom moet je ná wait() altijd checken of de gebeurtenis waar je op wachtte zich voorgedaan heeft.

Waar je op wacht, is dan echter niet Thread.interrupted(), maar een specifieke conditie. Bijvoorbeeld als je in de ene thread een bestand download, en in de tweede thread daar iets mee wil doen, dan zou je dat kunnen schrijven als:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
URL url = "..";
String content = null;

void thread1() {
    String s = downloadToString(url);
    synchronized(url) {
        content = s;
        url.notify();
    }
}

void thread2() {
    synchronized(url) {
        while (content == null) url.wait();
    }
    System.out.println("Downloaded:", content);
}

In dit geval gebruik je content != null dus als conditie om in thread2 te verifiëren dat de operatie in thread1 voltooid is.

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Bedankt, voor de reacties
In het voorbeeld waar ik de hele tijd naar heb zitten kijken, wordt aan het eind shutdownNow() van ExecuterService aangeroepen, en dan treedt er wel een interrupt op. Als ik shutdownNow weghaal blijf het voorbeeld programma ook doorlopen.

Verder begint veel me nu lanzaam duideluik te worden, interrups hebben totaal niks met notify/wait te maken.

Acties:
  • 0 Henk 'm!

  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Executie gaat na een wait() alleen verder als er een notify() of notifyAll() is geweest. In alle andere gevallen geeft het een exception. Wanneer er meer notify()s dan wait()s kunnen zijn (bijv. omdat een notifyAll()) gebruikt wordt), dan zul je een wait() een loop tegenkomen.

Een loop is dus niet altijd nodig. Het is echter belangrijk je bij elke wait af te vragen:
1) of er dan altijd een notify() of notifyAll() gaat komen: er dus altijd precies gelijk of meer notify()s dan wait()s zijn.
2) of de conditie waarop je wacht altijd vervuld zal zijn wanneer de thread weer wakker wordt. Tussen een notify() van een thread en het hervatten van de wait() van een andere thread kan arbitraire hoeveelheid tijd zitten, waarin evt. andere threads de conditie weer ongeldig gemaakt hebben. Als dat zo is, dan zul je de conditie opnieuw moeten checken en eventueel het proces moeten herhalen (zie punt 1)

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
Infinitive schreef op maandag 28 februari 2011 @ 09:02:
Executie gaat na een wait() alleen verder als er een notify() of notifyAll() is geweest.
Dat is juist niet het geval, en het is precies waar whosvegas ook door in de war raakte. Het is mogelijk dat wait() returnt zonder corresponderende notify() (of notifyAll()). Om voor de volledigheid even te quoten uit de API docs:
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:

Java:
1
2
3
4
5
     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }

Acties:
  • 0 Henk 'm!

  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Soultaker schreef op maandag 28 februari 2011 @ 14:10:
Dat is juist niet het geval, en het is precies waar whosvegas ook door in de war raakte.
Je hebt gelijk. Sinds versie 1.5 staat dit er in de api-documentatie bij. Dat is wel even schrikken. In oudere versies was het gedrag ofwel anders, of het was toen nog niet gedocumenteerd.

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


Acties:
  • 0 Henk 'm!

  • Remus
  • Registratie: Juli 2000
  • Laatst online: 15-08-2021
Waarschijnlijk was het altijd al zo, maar hebben ze dat nu expliciet gedocumenteerd. Overigens moet je toch altijd controle op je wait-conditie, omdat de reden van de notificatie niet noodzakelijk de reden was waarop gewacht werd of die reden inmiddels weer ongeldig is geworden (bijvoorbeeld door een andere thread die er ook op aan het wachten was).

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
Officieel was het altijd al zo. In de praktijk komen in de HotSpot VM (dat is dus de VM die bijna iedereen gebruikt op z'n PC) spurious wakeups niet voor (ook uit compatibiliteit met programma's die geschreven zijn vóór dit expliciet gedocumenteerd werd) en dat maakt het juist zo verradelijk: met eenvoudig testen zijn dit soort fouten dus niet te vinden.

Zelfs op alternatieve VM implementaties (en die zijn er volop, niet alleen voor de PC, maar juist ook voor telefoons en tablet computers en zo), waar spurious wakeups dus wél kunnen voorkomen, zijn dit soort fouten heel erg moelijk te debuggen, omdat de bugs meestal niet deterministisch en dus moeilijk te reproduceren zijn.

Het beste is dus om altijd rekening te houden met spurious wakeups, ongeacht welke VM je vandaag toevallig gebruikt. :)

[ Voor 5% gewijzigd door Soultaker op 28-02-2011 15:47 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Nog een voorbeeldje
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class CalcAndWait {
    public static void main(String[] args) {
        Calc calc=new Calc();
        new Thread(new DivisionThread(calc)).start();
        new Thread(new SetThread(calc)).start();
    }
}

class SetThread implements Runnable {
    private final Calc calc;
    
    public SetThread(Calc calc) {
        this.calc=calc;
    }
    
    @Override
    public void run() {
        calc.setValue(10);
    }   
}

class DivisionThread implements Runnable {
    private final Calc calc;
    
    public DivisionThread(Calc calc) {
        this.calc=calc;
    }
    
    @Override
    public void run() {
        System.out.println(calc.division(2));
    }
    
}
class Calc {
    private Integer value=null;
    
    public synchronized void setValue(Integer value) {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.value=value;
        notifyAll();
    }
    
    public synchronized Integer division(Integer division) {
        try {
            while (value==null) {
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return value/division;
    }
}

Uitkomst is keurig: 5 :)

Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
En nu graag met [code=java][/code] tags :P

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Soultaker schreef op maandag 28 februari 2011 @ 20:08:
En nu graag met [code=java][/code] tags :P
Zo ziet het er idd veel beter uit :)
Pagina: 1