[java] leuk concurrency raadsel

Pagina: 1
Acties:
  • 131 views sinds 30-01-2008
  • Reageer

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Ik ben het Java Memory Model aan het bestuderen en ik ben weer tegen een leuk raadsel aangelopen. Als de volgende class in een multithreaded omgeving gebruikt gaat worden, wat zou dan de output zijn? (als we er even vanuit gaan dat foo maar 1 keer wordt aangeroepen).


Java:
1
2
3
4
5
6
7
8
9
10
public class A{
   private int x = 10;

   public void foo(){
      x = 20;
      synchronized(this){
         System.out.println( x );
      }
   }
}

  • martennis
  • Registratie: Juli 2005
  • Laatst online: 16-01 14:17
Alarmnummer schreef op zondag 10 september 2006 @ 16:49:
Als de volgende class in een multithreaded omgeving gebruikt gaat worden, wat zou dan de output zijn? (als we er even vanuit gaan dat foo maar 1 keer wordt aangeroepen).
En x is (op een of andere manier) wel te benaderen/wijzigen door andere threads?

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
martennis schreef op zondag 10 september 2006 @ 17:25:
[...]
En x is (op een of andere manier) wel te benaderen/wijzigen door andere threads?
Nee.. Dit is alles wat er is. En ook geen gedonder met reflectie, bytecode modificatie of andere narigheid.

[ Voor 5% gewijzigd door Alarmnummer op 10-09-2006 17:45 ]


  • Kuhlie
  • Registratie: December 2002
  • Niet online
Zal ik dan, omdat het kan, voor het logische antwoord '20' gaan? :-)

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 17:29

RayNbow

Kirika <3

Ik weet eigenlijk weinig af van concurrency, dus dit is puur een gok...
Java:
5
      x = 20;

Java:
6
7
8
      synchronized(this){
         System.out.println( x );
      }

Ik gok dat de compiler deze 2 blokken misschien mag rearrangen, waardoor de output zowel 10 als 20 kan zijn.

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


  • Paul
  • Registratie: September 2000
  • Laatst online: 17:02
Okee, wat heb je nu weer gevonden? :P Ik zou zeggen dat er 20 uitkomt, maar je vorige topic in gedachten houdend zal het wel weer 10 zijn :+

"Your life is yours alone. Rise up and live it." - Richard Rahl
Rhàshan - Aditu Sunlock


  • martennis
  • Registratie: Juli 2005
  • Laatst online: 16-01 14:17
eigenlijk zou je moeten syncen op x, maar x is van een primitief type dus dat wil niet, maar dat terzijde. oplossing is natuurlijk om van x een Integer object te maken.

ik kan er niet echt achter komen wat nou het verschil zou zijn in een single of multi-threaded omgeving. Het wat echt uit maakt, is wanneer andere threads ook de outputstream van System.out gebruiken. (wederom incorrecte synchronisatie :P)

wat wel voor kan komen bij meedere threads (als x gelezen kan worden), is een race conditie, waarin x nog als 10 gelezen wordt, terwijl x officieel al wel veranderd is.

link

Ik gok toch dat de uitvoer van deze versie "20" is, omdat er geen data wisselingen plaats vinden en x niet te lezen of schrijven is door andere threads.

Edit: ben nog wel in dubio, want als de PrintStream van System op een andere thread wordt gebruikt dan waarin foo( ) wordt uitgevoerd, dan zou het ook wel "10" kunnen zijn, maar volgens mij gebeurd dat niet

[ Voor 10% gewijzigd door martennis op 10-09-2006 18:32 ]


  • BoutWout
  • Registratie: Mei 2000
  • Laatst online: 17-01 14:52
The answer is 42

We zitten hier niet in de HK...

[ Voor 64% gewijzigd door RobIII op 10-09-2006 20:40 ]

Red een boom, eet een bever


  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Mijn verstand zegt 20
Mijn gevoel zegt 0

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
RayNbow schreef op zondag 10 september 2006 @ 17:48:
Ik weet eigenlijk weinig af van concurrency, dus dit is puur een gok...
Java:
5
      x = 20;

Java:
6
7
8
      synchronized(this){
         System.out.println( x );
      }

Ik gok dat de compiler deze 2 blokken misschien mag rearrangen, waardoor de output zowel 10 als 20 kan zijn.
Dat is (gelukkig) niet mogelijk. Instructie reordening mag alleen als het binnen de thread niet zichtbaar is.

code:
1
2
3
4
5
void bla(){
  int a =10;
  int b = 20;
  System.out.println(a+" "+b);
}


dat zou zonder problemen gereordenen mogen worden naar:

code:
1
2
3
4
5
void bla(){
  int b = 20;
  int a =10;
  System.out.println(a+" "+b);
}


Maar het volgende reordening is niet geldig

voor reordening
code:
1
2
3
4
5
6
7
8
9
void bla(){
  int a = 0;
  int b = 0;

  a = 10;
  b = 2*a;
  
  System.out.println(a+" "+b);
}


Na reordening
code:
1
2
3
4
5
6
7
8
9
void bla(){
  int a = 0;
  int b = 0;

  b = 2*a;
  a = 10;
  
  System.out.println(a+" "+b);
}


Een programma moet dus zijn as-if-serial semantics behouden, want anders zou je helemaal niet meer op je programma aan kunnen.

  • prototype
  • Registratie: Juni 2001
  • Niet online

prototype

Cheer Bear

martennis schreef op zondag 10 september 2006 @ 18:15:
eigenlijk zou je moeten syncen op x, maar x is van een primitief type dus dat wil niet, maar dat terzijde. oplossing is natuurlijk om van x een Integer object te maken.
Neen, dat is niet een goede oplossing, aangezien het Integer object immutable is EN er ook sprake is van een assignment. Als je x een Integer object zou maken, en vervolgens erop zou synchen, en je assigned vervolgens een andere referentie aan x, dan wachten alle andere threads die in de lock wachten op de inmiddels OUDE lock van de waarde van x. De lock heeft in feite weinig nut dus, en kan nog wel meer problemen opleveren. Voor zulke situaties zijn monitoren bedacht, e.g.

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class A
{
    public static final Object MONITOR = new Object();
    private int x;

    public void foo(int x)
    {
        synchronized(MONITOR)
        {
            this.x = x;
        }
    }
}


Par example.
Ontopic:
Je zegt dat de class in een multithreaded environment gebruikt wordt en dat foo eenmaal geinvoked wordt. Moet ik dit interpreteren als zijnde dat een instance van A door meerdere threads de foo 1maal geinvoked van wordt?
Het zit hem meer in volgens mij dat foo nu invoked kan worden voordat de constructor klaar is met de assignment van x=10, waardoor het dus kan zijn dat je iig of 20 hebt of 10. Ik twijfel nu of je alleen ook nog 0 kan krijgen, daar moet ik meer info over hebben hoe dat in de constructor verloopt, maar volgens mij niet.
[00:59] <prototype> nee 0 kan er niet uitkomen lijkt me
[00:59] <prototype> het is OF 20 OF 10
[01:00] <prototype> tenminste
[01:00] <prototype> hoe doet de constructor dat
[01:00] <prototype> ik neem aan dat er sowiesow eerst geheugen gereserveerd moet worden
[01:00] <prototype> voor x
[01:00] <prototype> voordat x=20 kan plaatsvinden
[01:03] <luite> mja ik zit ff met die default value
[01:04] <luite> 0 kan er idd niet uit komen
[01:06] <luite> ok
[01:06] <luite> het is 10 of 20 idd
[01:07] <luite> C:\temp>cat TestDefault.java
[01:07] <luite> class TestDefault { private int x = 10;
[01:07] <luite> }
[01:07] <luite> C:\temp>javac TestDefault.java
[01:07] <luite> C:\temp>javap -c TestDefault
[01:07] <luite> Compiled from "TestDefault.java"
[01:07] <luite> class TestDefault extends java.lang.Object{
[01:07] <luite> TestDefault();
[01:07] <luite> Code:
[01:07] <luite> 0: aload_0
[01:07] <luite> 1: invokespecial #1; //Method java/lang/Object."<init>":()V
[01:07] <luite> 4: aload_0
[01:07] <luite> 5: bipush 10
[01:07] <luite> 7: putfield #2; //Field x:I
[01:07] <luite> 10: return
[01:07] <luite> }
[01:07] <luite> oftewel de default value assignment wordt gewoon als constructor code gegenereerd
[01:15] <luite> stel dat de default value niet constructor issue was, zou je altijd 20 krijgen
[01:15] <luite> als het dat wel is zou je 10 of 20 kunnen krijgen
[01:16] <luite> 0 kun je nooit krijgen want per thread moeten de acties 'serieel' zijn, de x=20 assignment moet dus altijd zichtbaar zijn
[01:16] <luite> het enige dat kan gebeuren is een andere assignment na die x=20 buiten het synchronized blok
[01:16] <luite> bij die lock moet hij de variableen reloaden, kan zijnd at hij dan dus merkt dat de x weer overwritten is
[01:16] <luite> maar dat kan niet met 0 gebeuren
;)

[ Voor 49% gewijzigd door prototype op 11-09-2006 01:17 ]


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 13-02 18:09
- Wanneer vind de toekenning x=10 plaats?
- Wat houdt synchronised( this ) in? Alle methods zijn synchronized?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • prototype
  • Registratie: Juni 2001
  • Niet online

prototype

Cheer Bear

farlane schreef op maandag 11 september 2006 @ 00:59:
- Wanneer vind de toekenning x=10 plaats?
- Wat houdt synchronised( this ) in? Alle methods zijn synchronized?
-x=10 vindt plaats in de impliciete constructor.
-synchronized(this) houdt in dat de synchronized block op zichzelf synchroniseert (op de instantie van A waar foo geinvoked wordt).

  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

(als we er even vanuit gaan dat foo maar 1 keer wordt aangeroepen)
foo wordt dus maar 1x aangeroepen in de hele multithreaded omgeving? Dan ga ik ervan uit dat dit gewoon 20 oplevert.

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 17:29

RayNbow

Kirika <3

Alarmnummer schreef op zondag 10 september 2006 @ 23:43:
Dat is (gelukkig) niet mogelijk. Instructie reordening mag alleen als het binnen de thread niet zichtbaar is.
Ah ok :)

* RayNbow zal later nog wel eens ff naar het probleem kijken...

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


  • Tubby
  • Registratie: Juni 2001
  • Laatst online: 13-02 09:53

Tubby

or not to be

Alarmnummer schreef op zondag 10 september 2006 @ 16:49:
Java:
1
2
3
4
5
6
7
8
9
10
public class A{
   private int x = 10; //1

   public void foo(){
      x = 20;//2
      synchronized(this){
         System.out.println( x );
      }
   }
}
Je hebt hier 2 mogelijke situaties
1. Thread 1 loopt en is na punt 2 gekomen wanneer Thread 2 begint te lopen en x overschrijft met 10. In dit geval zou je een redelijk random patroon 10/20 waarden in je system.out moeten vinden (even random als je thread pattern).

2. x zou als het goed is een variabele per instantie per thread moeten zijn. Daarvoor zou de System.out alleen 20's moeten geven omdat de methode misschien wel synchronized is maar wel de variabele per instantie/thread print.

Ik denk dat het situatie 2 is, ik denk dat situatie 1 pas gaat voorkomen als x static is.

tubby.nl - Artes Moriendi - q1 - bf1942 - WoT - pubg - LinkedIN


  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Tubby schreef op maandag 11 september 2006 @ 07:15:
Ik denk dat het situatie 2 is, ik denk dat situatie 1 pas gaat voorkomen als x static is.
Eensch.

Maar dan nog, hoe groot is de kans dat dit voorkomt? Dat lijkt me interessanter en reëler dan de vraag wat de output überhaupt zou zijn.

  • DaRKie
  • Registratie: December 2001
  • Laatst online: 12-02 09:57
Tubby schreef op maandag 11 september 2006 @ 07:15:
[...]
Ik denk dat het situatie 2 is, ik denk dat situatie 1 pas gaat voorkomen als x static is.
Als x static is, wordt die slechts 1x aangemaakt dus ook maar 1x geinitialiseerd dus enkel daar wordt die op 10 gezet en daarna nooit meer.

Ik zie dan ook niet in welke andere mogelijk er zou zijn dan dat x steeds als 20 afgeprint wordt.

  • Tubby
  • Registratie: Juni 2001
  • Laatst online: 13-02 09:53

Tubby

or not to be

Java:
1
2
3
4
5
6
7
8
9
10
public class A{
   private int x = 10; //1

   public void foo(){
      x = Math.random();//2, zal niet hele correct zijn, maar voor het idee
      synchronized(this){
         System.out.println( x );
      }
   }
}

Stel class A is geladen in een SingleTon en method foo() wordt vanuit een multithreaded omgeving aangeroepen.

Dan heb je hetzelfde probleem als dat je var x static zou zijn. Aangezien alle thread's dezelfde variabele bewerken weet je dus niet zeker dat x dezelfde x nog is op het moment dat je 'm print.

Magoed, zoals BalusC al zei, niet echt boeiend voor praktijk situaties omdat als je daar in deze situatie komt je de assignment ook synchronized maakt.
- java blij: het gaat goed
- ik blij: want het gaat goed
- klant blij: het is opgelost
- baas blij: het heeft weinig tijd gekost
:+
DaRKie schreef op maandag 11 september 2006 @ 07:44:
[...]


Als x static is, wordt die slechts 1x aangemaakt dus ook maar 1x geinitialiseerd dus enkel daar wordt die op 10 gezet en daarna nooit meer.

Ik zie dan ook niet in welke andere mogelijk er zou zijn dan dat x steeds als 20 afgeprint wordt.
Dat is alleen het geval als je static final doet. static variabelen zonder de final modifier kun je gewoon wijzigen.

[ Voor 22% gewijzigd door Tubby op 11-09-2006 07:47 ]

tubby.nl - Artes Moriendi - q1 - bf1942 - WoT - pubg - LinkedIN


  • DaRKie
  • Registratie: December 2001
  • Laatst online: 12-02 09:57
Ik zeg toch niet dat je hem niet kan wijzigen? Ik zeg dat een static var slechts 1x geinitialiseerd wordt (wat is anders het nut ervan als hij telkens opnieuw geinit wordt? :)). En in dit voorbeeld wordt die dus maar 1x op 10 gezet en daarna nooit meer (wordt natuurlijk nog wel op 20 gezet maar niet meer op 10 ongeacht of er 1 of 1000 instanties aangemaakt worden)

[ Voor 10% gewijzigd door DaRKie op 11-09-2006 07:52 ]


  • Tubby
  • Registratie: Juni 2001
  • Laatst online: 13-02 09:53

Tubby

or not to be

Klopt, daarom is het voorbeeld ook interessanter met een random toewijzing :)

tubby.nl - Artes Moriendi - q1 - bf1942 - WoT - pubg - LinkedIN


  • DaRKie
  • Registratie: December 2001
  • Laatst online: 12-02 09:57
Mja, je weet dan nooit wat het resultaat kan zijn zelfs niet als je het 1x uitvoert, volgens mij is het volgende interessanter:

Java:
1
2
3
4
5
6
7
8
9
10
public class A{ 
   private int x = 10; //1 

   public void foo(){ 
      x += 20; //2
      synchronized(this){ 
         System.out.println( x ); 
      } 
   } 
}

We gaan er vanuit dat elke thread 1 instantie maakt en 1x foo() aanroept.

Zo is er geen enkel probleem, als je echter x static maakt dan heb je een probleem.
Je zou bv dit kunnen krijgen:
30
50
70
90

Maar ook dit kan:
30
90
90
90

of:
90
90
90
90

Je weet dus niet wat er uitgeprint wordt, enkel dat het laatste afgeprinte getal >= zal zijn aan het vorige (correct me als ik ergens fout ben)

Verwijderd

Waarom zou hier geen 20 uitkomen? Zodra foo eenmaal gecalled is, vanuit welke thread dan ook wordt x eerst 20, waarna er eventueel gewacht wordt op synchronisatie. Maar zodra de println wordt geexecute, is x in alle gevallen 20.

Overigens heeft de synchronized(this) hier niet bijster veel nut aangezien System.out.println ook gewoon synchroniseert (maar dan op System.out) iirc.
DaRKie schreef op maandag 11 september 2006 @ 09:05:We gaan er vanuit dat elke thread 1 instantie maakt en 1x foo() aanroept.
Righttt, overigens is je verhaal totaal _niet_ interessant (als in: dat is basic synchronisatie stuff en heeft niks met dit topic te maken, dat kennelijk over eoa vaag unexptected behaviour gaat adhv de specs).

[ Voor 34% gewijzigd door Verwijderd op 11-09-2006 09:43 ]


Verwijderd

Heeft het te maken met het main memory en dat "x = 20" daarmee niet gelijk getrokken wordt?

edit:
Ik weet namelijk niet of het process na "x=20" kan en mag worden uitgevoerd op een andere processor...

[ Voor 38% gewijzigd door Verwijderd op 11-09-2006 09:40 ]


  • momania
  • Registratie: Mei 2000
  • Laatst online: 17:37

momania

iPhone 30! Bam!

Alarmnummer kennende zal dit niet alleen met concurrency te maken hebben, maar meer met concurreny-persistency ( en dus het memory model van de vm ) :P

Ik denk dat een synchonized code block altijd uit main memory leest. De int x hoeft niet direct naar main memory te worden geschreven door de vm aangezien deze niet volatile of final is.

Hoogstwaarschijnlijk komt er dus gewoon 10 uitrollen. :)

Neem je whisky mee, is het te weinig... *zucht*


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 09-12-2025
Dat klinkt idd. logisch. Ik ben wel benieuwd of dit zo is, op zich wel belangrijke informatie.

Noushka's Magnificent Dream | Unity


  • DaRKie
  • Registratie: December 2001
  • Laatst online: 12-02 09:57
Verwijderd schreef op maandag 11 september 2006 @ 09:33:
Waarom zou hier geen 20 uitkomen? Zodra foo eenmaal gecalled is, vanuit welke thread dan ook wordt x eerst 20, waarna er eventueel gewacht wordt op synchronisatie. Maar zodra de println wordt geexecute, is x in alle gevallen 20.

Overigens heeft de synchronized(this) hier niet bijster veel nut aangezien System.out.println ook gewoon synchroniseert (maar dan op System.out) iirc.


[...]

Righttt, overigens is je verhaal totaal _niet_ interessant (als in: dat is basic synchronisatie stuff en heeft niks met dit topic te maken, dat kennelijk over eoa vaag unexptected behaviour gaat adhv de specs).
Het is indd redelijk basic allemaal, daarom dat ik voor jou reactie al 3x gezegd heb dat 20 de output gaat zijn volgens mij.

Mijn verhaal was voor Tubby zijn aanpassing bedoelt. Mijn code is net zo basic als die van de TS.

Vraag me nog steeds af wanneer de clou gaat komen van dit "vraagstuk" want op het eerste zicht zie ik niets speciaal en alarmnummer lost niet veel.

  • sirdupre
  • Registratie: Maart 2002
  • Laatst online: 27-04-2025
momania schreef op maandag 11 september 2006 @ 09:49:
Alarmnummer kennende zal dit niet alleen met concurrency te maken hebben, maar meer met concurreny-persistency ( en dus het memory model van de vm ) :P

Ik denk dat een synchonized code block altijd uit main memory leest. De int x hoeft niet direct naar main memory te worden geschreven door de vm aangezien deze niet volatile of final is.

Hoogstwaarschijnlijk komt er dus gewoon 10 uitrollen. :)
Maar als dat het geval is, dan zou dat zich toch altijd voor moeten doen en is er geen verschil tussen 1 of meerdere threads? Dus dan is het eigenlijk puur een memory raadsel en geen concurrency raadsel (hoewel er taalconstructies voor concurrency gebruikt worden...)

[ Voor 49% gewijzigd door sirdupre op 11-09-2006 10:18 ]


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 17:10

Creepy

Tactical Espionage Splatterer

Het is dan wel degelijk een concurrency probleem aangezien het alleen kan optreden op een SMP systeem. Met 1 processor zal het niet uitmaken aangezien elke thread op dezelfde cpu draait en het dus niet uitmaakt of iets in de cache of in main memory zit.

Overigens gok ik hetzelfde als momania. Op die manier is er niet gegarandeerd dat er 20 uit komt rollen. Indien dit het geval is: Alarmnummer: Er zijn toch wel platformen waarbij explicite memory barriers gebruikt worden indien ondersteunt? Het lijkt me niet dat elk platform hier last van heeft.

[ Voor 36% gewijzigd door Creepy op 11-09-2006 10:30 ]

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
DaRKie schreef op maandag 11 september 2006 @ 09:56:
[...]
Vraag me nog steeds af wanneer de clou gaat komen van dit "vraagstuk" want op het eerste zicht zie ik niets speciaal en alarmnummer lost niet veel.
Ik hoop dat ik in de loop van de dag meer duidelijk heb. Ik heb op de concurrency mailinglist namelijk hetzelfde probleem voorgelegd en niet iedereen is het er over eens.

Mijn vermoeden is dat de volgende output valid is:
0, 10 en 20.

Maar hier ga ik nog op terug komen.

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
SirDupre schreef op maandag 11 september 2006 @ 10:17:
[...]
Maar als dat het geval is, dan zou dat zich toch altijd voor moeten doen en is er geen verschil tussen 1 of meerdere threads?
Er zou wel een verschil kunnen zijn. Als het object door 1 thread word aangemaakt en gebruikt dan is het antwoord uiteraard 20.
Dus dan is het eigenlijk puur een memory raadsel en geen concurrency raadsel (hoewel er taalconstructies voor concurrency gebruikt worden...)
Je zit daar ook precies op het snijvlak van het memory model en concurrency en imho zijn die heel nauw aan elkaar verbonden. Het synchronized statement doet namelijk meer dan alleen een lock aanvragen.

[ Voor 5% gewijzigd door Alarmnummer op 11-09-2006 10:42 ]


  • DaRKie
  • Registratie: December 2001
  • Laatst online: 12-02 09:57
Je zou denken dat de JVM dergelijke problemen voor je oplost niet? Dat is toch zo een beetje het doel van de VM, hardware onafhankelijk te zijn.

Als het echt zo zou zijn dat je 10 kan krijgen, dan is dat toch wel redelijk triest vind ik. Als je zo een stukje code ergens hebt staan en je moet het gaan debuggen ...

Ik kijk in ieder geval uit naar het antwoord :)

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
DaRKie schreef op maandag 11 september 2006 @ 11:17:
Je zou denken dat de JVM dergelijke problemen voor je oplost niet? Dat is toch zo een beetje het doel van de VM, hardware onafhankelijk te zijn.
De JVM die moet zo nu en dan de regels wat 'relaxen' om allerlei compiler optimalisaties mogelijk te maken. En je moet verder niet vergeten dat je hier sowieso al fout bezig bent aangezien x in een multithreaded omgeving geen enkele bescherming heeft. Dus dit is iets dat je in de praktijk nooit hoort te doen.

  • Paul
  • Registratie: September 2000
  • Laatst online: 17:02
Ik moet bekennen dat ik niet echt bekend ben met multithreaded omgevingen, maar deze mag je me even uitleggen als je wilt :P

x is een lokale variabele in klasse A. Iedere instantie van die klasse, multithreaded of niet, heeft zijn eigen x, een andere instantie (of klasse) mag daar nooit aankomen, anders dan via methoden van die instantie (x is immers private).

Welke beveiliging heeft X nog meer nodig dan? Wat is het voor onzin dat je bij een x := 20; gevolgd door een System.Out(x); iets anders kan krijgen dan 20? Als het iets is, dan is het een bug lijkt me zo?

Ik ben niet bekend hoe dit in andere talen zit, en ik wil er best aan geloven dat je bij multithreaded programmeren iets beter na moet denken over scope en toegangkelijkheid en race condities enzo, maar je zou bijna het beeld krijgen dat Java totaal ongeschikt is voor multithreading, omdat je er nooit vanuit kunt gaan dat x na een x:=20 ook daadwerkelijk 20 is 8)7

"Your life is yours alone. Rise up and live it." - Richard Rahl
Rhàshan - Aditu Sunlock


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Paul Nieuwkamp schreef op maandag 11 september 2006 @ 12:16:
Ik moet bekennen dat ik niet echt bekend ben met multithreaded omgevingen, maar deze mag je me even uitleggen als je wilt :P

x is een lokale variabele in klasse A.
het is geen lokale variable, het is immers een field. De variable kan dus door meerdere threads op een moment worden aangesproken.
Welke beveiliging heeft X nog meer nodig dan? Wat is het voor onzin dat je bij een x := 20; gevolgd door een System.Out(x); iets anders kan krijgen dan 20? Als het iets is, dan is het een bug lijkt me zo?
Ik ga hier nog op terug komen :) Maar het heeft te maken met allerlei optimalisaties (compiler, cpu etc) die nodig zijn om een systeem snel te kunnen laten draaien.
Ik ben niet bekend hoe dit in andere talen zit, en ik wil er best aan geloven dat je bij multithreaded programmeren iets beter na moet denken over scope en toegangkelijkheid en race condities enzo, maar je zou bijna het beeld krijgen dat Java totaal ongeschikt is voor multithreading, omdat je er nooit vanuit kunt gaan dat x na een x:=20 ook daadwerkelijk 20 is 8)7
Java is heel geschikt voor multithreaded applicaties (vooral nu met de verbeterde JMM en de concurrency library en nio (non blocking io vooral)). Maar het voorbeeld houdt zich niet aan de concurrency regels en dat is de oorzaak van de onverwachte antwoorden. Dit soort dingen zou je absoluut niet in je systeem willen aantreffen, maar het is op zich wel leuk om te weten wat er nou onder de grond allemaal kan gebeuren als je niet voldoende synchroniseerd.

Dus als jij je aan de regels houdt, dan heb je dit soort onvoorspelbaar gedrag niet/minder.

[ Voor 3% gewijzigd door Alarmnummer op 11-09-2006 12:42 ]


  • BalusC
  • Registratie: Oktober 2000
  • Niet online

BalusC

Carpe diem

Maar het voorbeeld houdt zich niet aan de concurrency regels
Dus .. synchronized(this) is in deze context uit den boze?

* BalusC kijkt even in http://java.sun.com/docs/...al/concurrency/sync.html:
Warning: When constructing an object that will be shared between threads, be very careful that a reference to the object does not "leak" prematurely. For example, suppose you want to maintain a List called instances containing every instance of class. You might be tempted to add the line

instances.add(this);

to your constructor. But then other threads can use instances to access the object before construction of the object is complete.
Heeft hier vast mee te maken :)

Een betere aanpak zou dan kunnen zijn:
Java:
1
2
3
4
5
6
7
8
9
10
public class A{
   private int x = 10;

   public void foo(){
      synchronized(this) {
         x = 20;
         System.out.println( x );
      }
   }
}
of
Java:
1
2
3
4
5
6
7
8
public class A{
   private int x = 10;

   public synchronized void foo(){
      x = 20;
      System.out.println( x );
   }
}

[ Voor 22% gewijzigd door BalusC op 11-09-2006 13:06 ]


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Creepy schreef op maandag 11 september 2006 @ 10:22:
Het is dan wel degelijk een concurrency probleem aangezien het alleen kan optreden op een SMP systeem. Met 1 processor zal het niet uitmaken aangezien elke thread op dezelfde cpu draait en het dus niet uitmaakt of iets in de cache of in main memory zit.
Ik geloof dat het probleem zich zelf kan voordoen op systemen met een enkele cpu. Door instruction reordening zou foo aangeroepen kunnen worden door een andere thread voordat de constructor is aangeroepen.

Hierdoor zou het kunnen voorkomen net na x=20 (thread2) en net voordat je het synchronized blok ingaat (ook thread2), a=10 door de 1e thread wordt aangeroepen. Dus vandaar dat x ook 10 kan worden.

Er is trouwens denk ik nog een manier om die x=10 te krijgen. Aangezien de x=20 assignment in local memory mag blijven staan en de synchronized statement effectief gezien de cache invalideerd, kan het voorkomen dat de laatste toekenning verloren gaat. Bij de eerst volgende read moet uit main memory worden gelezen en dan zou je de waarde 10 kunnen zien.

Over deze ben ik trouwens nog niet 100% zeker.
Overigens gok ik hetzelfde als momania. Op die manier is er niet gegarandeerd dat er 20 uit komt rollen. Indien dit het geval is: Alarmnummer: Er zijn toch wel platformen waarbij explicite memory barriers gebruikt worden indien ondersteunt? Het lijkt me niet dat elk platform hier last van heeft.
Het memory model moet platform onafhankelijk zijn. Dit voorbeeld is dus niet bedoelt om op een specifiek platform een problemen aan te tonen, maar puur over wat er mogelijk is binnen het JMM.

  • Paul
  • Registratie: September 2000
  • Laatst online: 17:02
Alarmnummer schreef op maandag 11 september 2006 @ 12:36:
het is geen lokale variable, het is immers een field. De variable kan dus door meerdere threads op een moment worden aangesproken.
Ok, hier ben je me al kwijt :P Ik ga eens opzoeken wat een field is. private (type) == lokale variabele, dacht ik altijd.

En zelfs met een getter of setter maakt het m.i. niet veel uit, ik get 10, sla die zelf ergens op, een andere thread set 20, die eerste thread rekent verder met 10. No big deal, 10 was een geldig antwoord.

Als ik 10 get, dit niet lokaal opsla, begin met rekenen, een andere thread set 20, en die eerste thread gaat verder met rekenen en get ergens waar hij hem weer nodig heeft weer de waarde (nu 20), ja dan krijg je vage dingen, maar dat lijkt me hier niet aan de orde?

Maar ik ga eens kijken wat een field is :P @ hieronder, ah, op die manier :)

"Your life is yours alone. Rise up and live it." - Richard Rahl
Rhàshan - Aditu Sunlock


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Paul Nieuwkamp schreef op maandag 11 september 2006 @ 12:52:
[...]
Ok, hier ben je me al kwijt :P Ik ga eens opzoeken wat een field is. private (type) == lokale variabele, dacht ik altijd.
code:
1
2
3
void bla(){
   int a = 10;
}


a is een local variable.

code:
1
2
3
class B{
   private int c;
}


c is een field/member variable.

[ Voor 11% gewijzigd door Alarmnummer op 11-09-2006 12:54 ]


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 09-12-2025
Om even volledig te zijn: een field/member variable wordt ook wel een instance variable genoemd. Dat je het even weet ;) Ik weet niet of er een officiele term is, maar je komt ze alledrie wel tegen.

Noushka's Magnificent Dream | Unity


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Alarmnummer schreef op maandag 11 september 2006 @ 10:33:
Mijn vermoeden is dat de volgende output valid is:
0, 10 en 20.
Het probleem is vergelijkbaar met je vorige topic, namelijk dat een andere thread idd al iets met de instantie kan doen voordat de ene thread die het object gemaakt heeft de constructor callt.

Wat ik me afvraag is waarom die 0 een speciale waarde heeft. Niet geinitialiseerd hoeft niet per se 0 te zijn, maar ik weet niet in hoeverre de vm verplicht wordt om z'n mem te zeroinitializen en z'n cache te flushen voordat een pointer naar het mem gebruikt mag worden. Aan de andere kant zou het verstrekkende gevolgen kunnen hebben als het niet verplicht zeroinitialized hoeft te zijn:
Java:
1
2
3
4
5
6
7
8
9
class MyClass
{
    String s;

    void foo()
    {
        System.out.println("s = " + s);
    }
}

Als s niet gegarandeerd zeroinitialized is (en pas op null wordt geinitializeerd in de constructor) dan wijst ie dus naar random geheugen en treed je dus al buiten de sandbox.

.edit: nee, die 0 kan niet eens. Er wordt nooit een 0 naar x geschreven, en dus zal er na de assignment van 20 niet ineens een 0 staan.

[ Voor 6% gewijzigd door .oisyn op 11-09-2006 14:53 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
.oisyn schreef op maandag 11 september 2006 @ 14:51:
[...]

Het probleem is vergelijkbaar met je vorige topic, namelijk dat een andere thread idd al iets met de instantie kan doen voordat de ene thread die het object gemaakt heeft de constructor callt.
Yep.. maar als extraatje zit er namelijk het gedrag van de synchronized statement bij. Die heeft afgezien van het verkrijgen van een lock (als je het blok ingaat) ook een andere werking die hier wat roet in het eten kan gooien.
Wat ik me afvraag is waarom die 0 een speciale waarde heeft. Niet geinitialiseerd hoeft niet per se 0 te zijn, maar ik weet niet in hoeverre de vm verplicht wordt om z'n mem te zeroinitializen en z'n cache te flushen voordat een pointer naar het mem gebruikt mag worden.
Dat is altijd zo (bij java).
.edit: nee, die 0 kan niet eens. Er wordt nooit een 0 naar x geschreven, en dus zal er na de assignment van 20 niet ineens een 0 staan.
Die synchronized statement die kan roet in het eten gooien door local memory te invalideren (op het moment dat je de lock krijgt) Hierdoor bestaat de kans dat x=20 (dat misschien nog in de cache staat en nog niet geflusht is naar main memory) verloren gaat. Als de x=10 nog niet is aangeroepen (reordening) of niet naar main memory is geschreven (visibility problemen), dan bestaat de kans dat x nog op 0 staat (in main memory) en dat dit de waarde gaat worden die op de console komt te staan.

Ik ben op dit moment nog bezig met wat onderzoek naar het synchronized gedrag (literatuur is er niet bepaald duidelijk over en zeker tegenstrijdig). Dus het kan zijn dat ik het mis heb.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Alarmnummer schreef op maandag 11 september 2006 @ 15:55:
Die synchronized statement die kan roet in het eten gooien door local memory te invalideren.
Dat lijkt me wel heel onwaarschijnlijk, dat betekent dat je geen enkel zinnig multithreaded app meer kan schrijven, omdat elke waarde die je schrijft dan weer ongedaan gemaakt kan worden omdat de writecache geinvalidate wordt. Dat zou gewoon niet voor moeten komen en anders is het een design flaw. Write cache invalidaten is sowieso een nutteloze actie, waarom zou een VM dat in hemelsnaam doen?

Of hij heeft een oude waarde in z'n readcache terwijl die in main mem wel al geupdate is, of hij heeft een nieuwe waarde in writecache die nog niet is uitgeschreven. In beide gevallen zou na de assignment van x=20 de waarde 20 óf in de cache óf in het geheugen moeten staan, en tenzij die tussentijds overschreven wordt door een andere CPU - die tevens z'n cache flusht - moet je ook weer gewoon 20 uitlezen bij een read op dat adres.

[ Voor 35% gewijzigd door .oisyn op 11-09-2006 16:03 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
.oisyn schreef op maandag 11 september 2006 @ 15:59:
[...]

Dat lijkt me wel heel onwaarschijnlijk, dat betekent dat je geen enkel zinnig multithreaded app meer kan schrijven, omdat elke waarde die je schrijft dan weer ongedaan gemaakt kan worden omdat de writecache geinvalidate wordt.
Het voorbeeld dat ik heb gegeven houd zich niet aan de regels. Dus vandaar dat je onverwacht gedrag kunt krijgen. Dit is dus code dat je gewoonlijk niet wilt hebben, het is puur ff als voorbeeld wat er gebeuren kan als je je niet aan de regels houd.
Dat zou gewoon niet voor moeten komen en anders is het een design flaw. Write cache invalidaten is sowieso een nutteloze actie, waarom zou een VM dat in hemelsnaam doen?
Kan je veilig data pasen van 1 thread naar een andere. Zelfde heb je bij volatile.

[ Voor 18% gewijzigd door Alarmnummer op 11-09-2006 17:18 ]


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Volgens mij is het 0 of 10 of 20, ik denk dat je gelijk hebt met je uitleg. Deze voorbeeld bug plukt overigens Findbugs zo uit je code. Findbugs is echt een fantastische tool als je de voorbeelden van Alarmnummer moeilijk vindt. ;)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Alarmnummer schreef op maandag 11 september 2006 @ 16:31:

Het voorbeeld dat ik heb gegeven houd zich niet aan de regels. Dus vandaar dat je onverwacht gedrag kunt krijgen. Dit is dus code dat je gewoonlijk niet wilt hebben, het is puur ff als voorbeeld wat er gebeuren kan als je je niet aan de regels houd.
Dus dit is in principe altijd fout in multithreaded omgevingen, ookal weet je dat je object maar door 1 thread gebruikt gaat worden?
Java:
1
2
3
4
5
6
7
8
9
class Foo
{
    int x;

    public void setX(int newX)
    {
        x = newX;
    }
}

de nieuwe waarde van x kan immers into the void verdwijnen door een mogelijke cache invalidate na de aanroep van setX. Een onwerkbare situatie dus, de enige oplossing is om al je variabelen volatile te maken. Eerlijk gezegd geloof ik dat gewoonweg niet :).
Kan je veilig data pasen van 1 thread naar een andere. Zelfde heb je bij volatile.
Een cache flush is niet hetzelfde als een cache invalidate. Bij een invalidate gooi je de data in de cache weg, waardoor al je changes dus ook weg zijn. Dat is wat anders dan een flush, waarbij write-caches uitgeschreven worden naar main mem.

En flush kan er in jouw voorbeeld niet voor zorgen dat de lokale waarde van 20 weer wordt overschreven met 0 - simpelweg omdat er nooit een 0 naar die plek in memory wordt geschreven nadat het stuk geheugen gereserveerd is. Het blijft dan 20, tenzij die 20 zich alleen nog in de writecache bevindt en die geinvalidate wordt, waardoor de volgende read weer een 0 oplevert. Maar writecaches invalidaten terwijl dat inbreuk maakt op wat je code doet creëert zoals ik al zei een onwerkbare situatie

[ Voor 23% gewijzigd door .oisyn op 12-09-2006 12:40 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 13-02 14:38
Ik hou het toch gewoon op 20, zelfs als je rare dingen in de constructor doet.

De code komt hier op neer:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class A {
    private int x;
    public A() {
        x = 10;
        // rest van de constructor
    }

    public void foo() {
        x = 20;
        synchronized(this) {
            System.out.println( x );
        }
    }
}

Over het algemeen is het bad practice om in de constructor de reference te laten ontsnappen naar andere threads. In zo'n geval kan een andere thread inderdaad 0 lezen in plaats van 10. Als je in de constructor een nieuwe thread start, worden lokale wijzigingen wel weggeschreven voordat de nieuwe thread gestart wordt, dacht ik. Maar beide dingen zijn dus 'bad practice'.

Maar niets van dit alles wijzigt het feit dat als foo() wordt uitgevoerd, de waarde van x wordt overschreven. Bij de synchronisatie wordt (tenminste conceptueel) alle relevante data uit het geheugen ververst, maar daarbij worden recente wijzigingen (zoals x=20) natuurlijk niet overschreven. Je print dus hoe dan ook 20.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Soultaker schreef op dinsdag 12 september 2006 @ 16:18:
Over het algemeen is het bad practice om in de constructor de reference te laten ontsnappen naar andere threads.
Het punt was dat de VM het naar zoiets kan compileren:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
A * a = 0;

void thread1()
{
    a = malloc(sizeof(A));
    new (a) A();
}

void thread2()
{
    a->foo();
}


Het gaat 'm dus om regel 5 en 6. De VM kan ervoor zorgen dat de pointer al aan een variabele wordt geassigned voordat de constructor is aangeroepen (natuurlijk wel al nadat vtable e.d. zijn geinitialiseerd, waardoor dit C++ voorbeeld iets anders is dan wat de VM doet, maar het gaat om het idee).

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 13-02 14:38
Volgens mij mag dat 'tegenwoordig' juist expliciet niet, en moet de compiler garanderen dat een reference pas (in het globale geheugen) toegekend wordt nadat de constructie plaatsgevonden heeft en de wijzigingen daarvan ook zichtbaar zijn. Eens even zoeken of ik dat terug kan vinden...

edit:
Hier gevonden, maar het geldt nadrukkelijk alleen voor assignments aan final fields. Gaat hier dus niet op, maar ik blijf erbij dat het antwoord gewoon 20 is, omdat dat lokaal geschreven wordt.

[ Voor 33% gewijzigd door Soultaker op 12-09-2006 16:59 ]


  • Salandur
  • Registratie: Mei 2003
  • Laatst online: 17:22

Salandur

Software Engineer

als ik het goed begrijp hebben ze bij sun vergeten te specificeren dat een (implicitie) constructor een atomaire actie is, terwijl de meeste programmeurs dit wel verwachten

wel een grappig probleem. kunnen we een keer werkende voorbeeldcode krijgen die je getest hebt en zo af en toe fout gaat?

Assumptions are the mother of all fuck ups | iRacing Profiel


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Soultaker schreef op dinsdag 12 september 2006 @ 16:44:
Volgens mij mag dat 'tegenwoordig' juist expliciet niet, en moet de compiler garanderen dat een reference pas (in het globale geheugen) toegekend wordt nadat de constructie plaatsgevonden heeft en de wijzigingen daarvan ook zichtbaar zijn. Eens even zoeken of ik dat terug kan vinden...

edit:
Hier gevonden, maar het geldt nadrukkelijk alleen voor assignments aan final fields.
Dat was juist het punt, en de variabele is niet final :)
maar ik blijf erbij dat het antwoord gewoon 20 is, omdat dat lokaal geschreven wordt.
thread1
- initialized mem van object, stored de ptr in mem
- callt constructor
thread2
- leest object ptr uit mem, callt foo()
- schrijft 20 naar x
thread1
- schrijft 10 naar x in de constructor
thread2
- synchronizet
- leest x uit main mem (nu 10), en output deze

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Soultaker schreef op dinsdag 12 september 2006 @ 16:44:
Volgens mij mag dat 'tegenwoordig' juist expliciet niet, en moet de compiler garanderen dat een reference pas (in het globale geheugen) toegekend wordt nadat de constructie plaatsgevonden heeft en de wijzigingen daarvan ook zichtbaar zijn. Eens even zoeken of ik dat terug kan vinden...

edit:
Hier gevonden, maar het geldt nadrukkelijk alleen voor assignments aan final fields. Gaat hier dus niet op, maar ik blijf erbij dat het antwoord gewoon 20 is, omdat dat lokaal geschreven wordt.
Door reordering is 10 sowieso mogelijk (het x=10 commando zou uitgevoerd kunnen worden na x=20). Ik zit alleen nog te kijken of 10 nog op een andere manier kan en of 0 mogelijk is.

[ Voor 7% gewijzigd door Alarmnummer op 12-09-2006 19:04 ]


  • Tubby
  • Registratie: Juni 2001
  • Laatst online: 13-02 09:53

Tubby

or not to be

.oisyn schreef op dinsdag 12 september 2006 @ 17:52:
[...]

Dat was juist het punt, en de variabele is niet final :)


[...]

thread1
- initialized mem van object, stored die in mem
- callt constructor
thread2
- leest object ptr uit mem, callt foo()
- schrijft 20 naar x
thread1
- schrijft 10 naar x in de constructor
thread2
- synchronizet
- leest x uit main mem (nu 10), en output deze
volgens mij schrijven thread1 en thread2 naar hun "eigen" x instantie, aangezien de var niet static is. Hierbij ga ik er ook vanuit thread1 en thread2 een eigen instantie hebben van deze class.

tubby.nl - Artes Moriendi - q1 - bf1942 - WoT - pubg - LinkedIN


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Tubby schreef op dinsdag 12 september 2006 @ 19:10:
[...]

volgens mij schrijven thread1 en thread2 naar hun "eigen" x instantie, aangezien de var niet static is.
Hierbij ga ik er ook vanuit thread1 en thread2 een eigen instantie hebben van deze class.
Als ze hun eigen instantie hebben is het voorbeeld niet leuk ;) Het gaat er juist om als het object door meerdere threads tegelijk wordt gebruikt.

  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Alarmnummer schreef op dinsdag 12 september 2006 @ 19:03:
[...]
Door reordering is 10 sowieso mogelijk (het x=10 commando zou uitgevoerd kunnen worden na x=20). Ik zit alleen nog te kijken of 10 nog op een andere manier kan en of 0 mogelijk is.
10 is ook mogelijk doordat de write met 10 geflushed kan zijn naar main memory, en de write met 20 niet, los van of het reordered is.

0 is ook mogelijk, omdat de default initializer van member fields altijd af gaat, en dit ook geflushed kan zijn naar main memory, maar de write met 10 of 20 niet. Zie de JLS 4.5.3. voor de definitie: "... if a class T has a field a that is an instance variable, then a new instance variable a is created and initialized to a default value...". Hier zie je nog een praktijkvoorbeeld. Het voorbeeld moet je dus eigenlijk lezen als "x = 0; x = 10". Overigens is die x = 0; wel happens before de constructor return, dus de vierde mogelijkheid: x = undefined, is niet mogelijk. :)

Simpel taaltje he, dat Java? >:)

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 13-02 14:38
Alarmnummer schreef op dinsdag 12 september 2006 @ 19:03:
Door reordering is 10 sowieso mogelijk (het x=10 commando zou uitgevoerd kunnen worden na x=20). Ik zit alleen nog te kijken of 10 nog op een andere manier kan en of 0 mogelijk is.
.oisyn's uitleg van 10 klopt wel (niet aan gedacht), en volgens mij kan 0 op precies dezelfde manier. Argh, dat klopt dus niet. Ik denk dat 0 niet kan, omdat die initialisatie gegarandeerd voor x=20 uitgevoerd wordt (en dat geldt voor die x=10 dus niet).

Wel een mooie demonstratie dat je niet moet proberen 'slim' te zijn, maar gewoon netjes moet synchroniseren, want de kans op fouten is veel te groot. (Behalve als er natuurlijk een dringende reden voor is.)

[ Voor 11% gewijzigd door Soultaker op 12-09-2006 20:26 ]


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
misfire schreef op dinsdag 12 september 2006 @ 20:23:
[...]

10 is ook mogelijk doordat de write met 10 geflushed kan zijn naar main memory, en de write met 20 niet, los van of het reordered is.
Dat dacht ik eerst dus ook (die 10 van die reordening had ik niet eens bij stil gestaan). Maar ik begin steeds meer te twijfelen.
0 is ook mogelijk, omdat de default initializer van member fields altijd af gaat, en dit ook geflushed kan zijn naar main memory, maar de write met 10 of 20 niet. Zie de JLS 4.5.3. voor de definitie: "... if a class T has a field a that is an instance variable, then a new instance variable a is created and initialized to a default value...". Hier zie je nog een praktijkvoorbeeld. Het voorbeeld moet je dus eigenlijk lezen als "x = 0; x = 10". Overigens is die x = 0; wel happens before de constructor return, dus de vierde mogelijkheid: x = undefined, is niet mogelijk. :)

Simpel taaltje he, dat Java? >:)
Ik begin er steeds meer in te geloven dat het hele verhaal met die cache invalidatie helemaal niet op gaat. Dus het droppen van die 20 zou dan helemaal niet mogelijk kunnen zijn en daardoor zou alleen 10 en 20 de mogelijke antwoorden kunnen zijn.

De documentatie is niet bepaald goed en ik ben door een boek (Concurrent en Realtime Java) op het verkeerde been gezet aangezien zij dus wel praten in termen van cache invalidatie.

Ik zie trouwens dat er nog veel meer documentatie op het internet te vinden is dat praat in termen van cache invalidatie en dat kon wel eens helemaal fout zijn.

[edit]
http://www.cs.umd.edu/~pu...oryModel/jsr-133-faq.html
En hier hebben ze het wel weer over cache invalidatie.

Beetje rommelig geregeld allemaal imho.

[ Voor 13% gewijzigd door Alarmnummer op 12-09-2006 21:01 ]


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Salandur schreef op dinsdag 12 september 2006 @ 17:24:
als ik het goed begrijp hebben ze bij sun vergeten te specificeren dat een (implicitie) constructor een atomaire actie is, terwijl de meeste programmeurs dit wel verwachten
Dat zijn de bij Sun niet vergeten, dat is een bewuste keuze. :) Als de constructor atomair zou zijn werkt het voorbeeld van Alarmnummer overigens nog steeds niet goed, er zitten namelijk meer gaten in.
wel een grappig probleem. kunnen we een keer werkende voorbeeldcode krijgen die je getest hebt en zo af en toe fout gaat?
Het vervelende van dit soort code-bugs is dat de meeste in vijf versies van Java op drie soorten processors altijd kan werken, en in één variant één op de miljard keer stuk gaat. Daarom is het zo handig om een statische code analyse als Findbugs te gebruiken, die een state machine bouwt van je code en dat valideert tegen de JMM regels. Er zijn maar weinig mensen die net als Alarmnummer Java threading issues als hobby hebben. ;)

Eén voorbeeld dat ik altijd gebruik om mensen angst in te boezemen is het volgende gebbetje dat altijd fout gaat:

Run dit stukje code onder Sun JDK 5 met "-server" command line optie:

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
public class EvilWhile {

    private static boolean test;
    
    private int counter;

    public static void main(String[] args) {
        EvilWhile evilWhile = new EvilWhile();        
        for (int i = 0; i < 100; i++) {
            test = true;
            evilWhile.asynchTestFalse();
            evilWhile.evil();
        }
    }
    
    public void asynchTestFalse() {
        new Thread(new Runnable() {
            public void run() {                
                test = false;
            }
        }).start();
    }
    
    public void evil() {        
        while (test) {
           counter++;
        }
    }
}

Dit gaat gegarandeerd in een oneindige loop komen, ongeacht je processor/os, altijd prijs. :) En het zijn niet eens reordering/cache invalidation problemen die dit veroorzaken. >:) Wie kan vertellen wat er gebeurt?

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
*ziet hem*

  • Tubby
  • Registratie: Juni 2001
  • Laatst online: 13-02 09:53

Tubby

or not to be

Alarmnummer schreef op dinsdag 12 september 2006 @ 19:23:
[...]

Als ze hun eigen instantie hebben is het voorbeeld niet leuk ;) Het gaat er juist om als het object door meerdere threads tegelijk wordt gebruikt.
Mja, dan is het altijd 20, aangezien hij alleen op 10 wordt gezet door de impliciete constructor, en de instantie wordt dus maar 1x aangemaakt ;)

tubby.nl - Artes Moriendi - q1 - bf1942 - WoT - pubg - LinkedIN


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

misfire schreef op dinsdag 12 september 2006 @ 20:23:
0 is ook mogelijk, omdat de default initializer van member fields altijd af gaat, en dit ook geflushed kan zijn naar main memory, maar de write met 10 of 20 niet. Zie de JLS 4.5.3. voor de definitie: "... if a class T has a field a that is an instance variable, then a new instance variable a is created and initialized to a default value...".
Ergo:

thread1
ptr = A_CreateAndInitializeMem(); // alloc en init mem
ptr.A(); // call constructor

thread2
ptr.foo();

ptr.foo() in thread2 can gecalled worden voor de constructor, maar niet voordat het geheugen is geinitialized en geflushed naar main mem. Het stuk dat jij quote zegt niets concurrency dus ik snap ook niet waarom je het aanhaalt. Het zegt slechts dat de variabele default-initialized zal zijn. Als die default initialize nog niet geflusht is voordat A_CreateAndInitializeMem() returnt krijg je een onwerkbare situatie zoals ik hier al aangaf - anders zou een fetch van een object pointer ook random garbage op kunnen leveren en dan is het hele sandbox idee van Java zoek.

Als de 0 al geflusht is, kan hij later nooit weer de waarde van 20 overschrijven - de flush wordt immers gedaan voordat de pointer naar het mem in mem gestored wordt en dus voordat thread2 de pointer fetcht om foo erop te invoken. Het kan slechts zo zijn dat de 20 nog in de writecache staat, terwijl de daadwerkelijke waarde in het geheugen nog 0 is. Dit is voor de thread die de waarde output geen probleem aangezien hij die zelf ook heeft geschreven (en dus levert een read van dat adres z'n eigen cache op). Invalidaten van de write cache is nutteloos omdat dat eveneens een onwerkbare situatie oplevert - alle writes die je doet kunnen dan zomaar ineens ongedaan gemaakt worden, ookal werken andere threads niet eens met jouw object.

.edit: waar ik nog niet aan heb gedacht is dat tussen de assignment van 20 aan x en de output van x een thread-switch plaats vindt, waarbij de thread bij vervolg op de ander CPU plaats kan vinden, en dus ineens met een andere cache werkt (eentje die niet de zojuist geschreven waarde 20 reflecteert). Dit is echter een situatie die nooit voor zal komen - dit maakt multithreaded applicaties namelijk zo goed als onmogelijk tenzij je standaard al je variabelen volatile definieert. Een kernel is dus zo goed als verplicht om readcaches te invalidaten en writecaches te flushen bij een threadswitch naar een andere CPU.
Alarmnummer schreef op dinsdag 12 september 2006 @ 20:54:
[edit]
http://www.cs.umd.edu/~pu...oryModel/jsr-133-faq.html
En hier hebben ze het wel weer over cache invalidatie.

Beetje rommelig geregeld allemaal imho.
Het staat er niet erg duidelijk, maar ik neem aan dat ze het daar hebben over de invalidatie van de read cache, die nodig is om changes in het geheugen door andere threads die nog niet zijn doorgevoerd in de lokale readcache zichtbaar te maken. Door de readcache te invalideren zal een volgende read op dat adres weer vanuit het geheugen moeten komen, en dus de actuele waarde reflecteren.

[ Voor 12% gewijzigd door .oisyn op 12-09-2006 21:59 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

@misfire:
ik gok dat de main thread door de scheduler bevoordeeld wordt aangezien de nieuwe thread wel gescheduled wordt als zijnde Runnable, maar de main-thread blijft draaien omdat ie steeds nog werk heeft.

@AlarmNummer:
als het op 1 enkele CPU draait dan zal die x = 20 in de cache of het geheugen zitten (of door de scheduling code verdrongen en naar main mem geflushed zijn) voor de 2de thread begint.

Als het op 2 CPU's draait, zou je verwachten dat wanneer x=20 gedaan wordt, op de 2de CPU de cache waarde van x geinvalideerd wordt. De andere CPU is dan dus verplicht om die waarde uit het geheugen te halen. Op zijn beurt zal de mem-controller er dan voor zorgen dat wanneer x opgevraagd wordt, dat die uit de cache van CPU 1 gehaald wordt (of toch dat die waarde geflushed wordt)

bovendien dacht ik altijd dat een constructor zo kon opgevat worden:
code:
1
2
3
alloceer geheugen (ptr = malloc)
initialiseer geheugen (operator new(ptr))
return pointer naar geheugen (return ptr)

Op die manier is het dus onmogelijk (tenzij je een thread start in de constructor, maar dat staat heir ook niet)

externe code zou op die manier pas aan het object raken wanneer de constructor volledig ten einde is.

ASSUME makes an ASS out of U and ME


  • Paul
  • Registratie: September 2000
  • Laatst online: 17:02
.oisyn schreef op dinsdag 12 september 2006 @ 17:52:
thread1
- initialized mem van object, stored de ptr in mem
- callt constructor
thread2
- leest object ptr uit mem, callt foo()
- schrijft 20 naar x
thread1
- schrijft 10 naar x in de constructor
thread2
- synchronizet
- leest x uit main mem (nu 10), en output deze
Dat deel van thread1: A a = new A(); thread2.a := a; ? Of hoe moet thread 2 bij die pointer kunnen komen? Door die globaler te maken en (mogelijk) door thread2 te laten gebruiken voordat a bestaat? Krijg je dan niet ontzettend veel nullPointerExceptions in threads? Of zet je dan bij ieder gebruik van a een if Assigned(a)-achtige constructie neer? Dan geeft die Assigned dus het verkeerde antwoord?

Vooralsnog lijkt me de scope van deze bug (als het er een is en er inderdaad iets anders dan 20 uit kan komen) erg beperkt en meer in het exposen van niet-geinitialiseerde objecten (wat volgens mij dus meestal een nullpointerexception op zou leveren) te zitten dan in de code die in de TS staat?

"Your life is yours alone. Rise up and live it." - Richard Rahl
Rhàshan - Aditu Sunlock


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Het gaat erom dat de VM de code zo kan reorganiseren dat hij de pointer naar het object met op dat moment nog zero-initialized variabelen kan storen in een globale variabele voordat hij de constructor op dat object aanroept.

De conditie is dat thread2 tussen het moment van de assignment aan de globale variabele en de assignment van 10 aan x, foo() aanroept op dat object via de globale variabele. De odds zijn natuurlijk vrij klein, maar het kan wel (zeker als je die tweede thread 100% op die tweede CPU laat lopen en in een busy loop blijft totdat die globale variabele z'n waarde krijgt)

Lees ook deze topic voor meer over dit onderwerp: \[disc/java] concurrency control instinker

Nou ben ik overigens wel benieuwd, er wordt gezegd dat er meerdere cases zijn die die 10 zouden laten zien in de output. Welke andere zijn er dan nog?

[ Voor 18% gewijzigd door .oisyn op 12-09-2006 22:14 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
.oisyn schreef op dinsdag 12 september 2006 @ 21:50:
ptr.foo() in thread2 can gecalled worden voor de constructor, maar niet voordat het geheugen is geinitialized en geflushed naar main mem. Het stuk dat jij quote zegt niets concurrency dus ik snap ook niet waarom je het aanhaalt. Het zegt slechts dat de variabele default-initialized zal zijn. Als die default initialize nog niet geflusht is voordat A_CreateAndInitializeMem() returnt krijg je een onwerkbare situatie zoals ik hier al aangaf - anders zou een fetch van een object pointer ook random garbage op kunnen leveren en dan is het hele sandbox idee van Java zoek.
Bij iedere instantie die wordt aangemaakt heb je de garantie dat de default initializers af gaan vóórdat iets anders gebeurt. In dat stukje omschrijving wordt inderdaad niet expliciet benoemd hoe dat werkt maar als je het per sé in C termen wil horen dan is het inderdaad zo dat er bij een nieuw object op de heap (main memory) een blok wordt gealloceerd wat expliciet genuld wordt. Je hebt dus geen kans om van een nog niet genuld object (random rommel) te lezen.
Het staat er niet erg duidelijk, maar ik neem aan dat ze het daar hebben over de invalidatie van de read cache, die nodig is om changes in het geheugen door andere threads die nog niet zijn doorgevoerd in de lokale readcache zichtbaar te maken. Door de readcache te invalideren zal een volgende read op dat adres weer vanuit het geheugen moeten komen, en dus de actuele waarde reflecteren.
De volgende read van x kan dan dus uit het geheugen komen, en als geen enkele write van x geflushed is dan kun je daar net zo goed een 0 krijgen. Als de 20 in de write cache altijd voorrang zou krijgen boven een read uit de read cache/main memory dan zou je altijd 20 krijgen, en ook nooit een 10 omdat die thread altijd de 20 als laatste heeft geschreven (reordering is altijd as if serial). Of er dirty checking plaats vindt op de caches is volgens mij niet gespecificeerd is in het JMM, ik heb daar in ieder geval nooit iets over gelezen. Als er iemand wel weet waar dit staat dan laat ik mij graag corrigeren. Maar voor zover ik nu weet mag je er niet vanuit gaan dat de read/write caches zich op die manier gedragen (in de praktijk zullen caches wel meestal zich zo gedragen zoals je beschrijft).

  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
HIGHGuY schreef op dinsdag 12 september 2006 @ 21:50:
@misfire:
ik gok dat de main thread door de scheduler bevoordeeld wordt aangezien de nieuwe thread wel gescheduled wordt als zijnde Runnable, maar de main-thread blijft draaien omdat ie steeds nog werk heeft.
Nee, ik zal maar verklappen wat er wel gebeurt. De JIT compiler van de Sun Java 5 server run-time herschrijft while loops waarbij de conditie variabelen tijdens de loop niet veranderd worden en waar in de body van de code ook geen synchronisatie-code aanwezig is. De JIT maakt van deze methode:
Java:
1
2
3
4
5
public void evil() {        
     while (test) {
        counter++;
     }
} 

Deze code:
Java:
1
2
3
4
5
6
7
public void evil() {        
    if (test) {
       while (true) {
          counter++;
       }
    }
} 

Om HotSpot zo gek te krijgen om de methode te JIT-ten wordt de methode in de loop verschillende keren aangeroepen. Als je de loop zou verkleinen naar bijvoorbeeld 1, dan zul je merken dat er meestal geen oneindige loop optreedt. Als je dit in de Java 5 client runtime zou doen dan zou het ook best kunnen werken, omdat de client JIT compiler deze optimalisatie (nog) niet doet.

Ik vind het wel een aardig voorbeeld om te laten zien hoe ontzettend je moet opletten als je niet goed omgaat met synchronisatie-mechanismen in Java. Naast reordering en memory-barriers heb je ook te maken met een JIT compiler die gewoon totaal andere code kan maken als hij niet weet dat er rekening met andere threads moet worden gehouden.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

misfire schreef op woensdag 13 september 2006 @ 11:29:
De volgende read van x kan dan dus uit het geheugen komen, en als geen enkele write van x geflushed is dan kun je daar net zo goed een 0 krijgen. Als de 20 in de write cache altijd voorrang zou krijgen boven een read uit de read cache/main memory dan zou je altijd 20 krijgen, en ook nooit een 10 omdat die thread altijd de 20 als laatste heeft geschreven (reordering is altijd as if serial).
Door reordering kan het idd niet dat dezelfde thread een 10 schrijft na de 20. Wat wel kan is dat door reordering de ene thread die het object heeft aangemaakt de x=10 nog niet heeft uitgevoerd op het moment dat de nadere thread de x=20 doet. Als die ene thread daarna de x=10 doet, en de andere thread komt bij de output, dan zal hij 10 lezen (mits de cache geflushed is, waarschijnlijk wel door de synchronize).
Of er dirty checking plaats vindt op de caches is volgens mij niet gespecificeerd is in het JMM, ik heb daar in ieder geval nooit iets over gelezen. Als er iemand wel weet waar dit staat dan laat ik mij graag corrigeren. Maar voor zover ik nu weet mag je er niet vanuit gaan dat de read/write caches zich op die manier gedragen (in de praktijk zullen caches wel meestal zich zo gedragen zoals je beschrijft).
Het is natuurlijk onzin dat de caches zich niet zo zouden kunnen gedragen. Dat betekent namelijk dat elke write gevolgd door een read garbage op kan leveren (of je nou in multithreaded omgevingen werkt of niet) en wordt je applicatie zo instabiel als wat. Een CPU die zo werkt heeft dus continu flushes nodig, en dan zal de compiler die beter ook maar genereren.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
.oisyn schreef op woensdag 13 september 2006 @ 12:22:
Door reordering kan het idd niet dat dezelfde thread een 10 schrijft na de 20. Wat wel kan is dat door reordering de ene thread die het object heeft aangemaakt de x=10 nog niet heeft uitgevoerd op het moment dat de nadere thread de x=20 doet. Als die ene thread daarna de x=10 doet, en de andere thread komt bij de output, dan zal hij 10 lezen (mits de cache geflushed is, waarschijnlijk wel door de synchronize).
Wat ik bedoel en waar jij het ook mee eens bent is dat de write cache dus niet altijd voorrang krijgt. Maar hoe weet die ene thread dan dat hij 10 moet inlezen en niet die 20 vast moet houden die nog in de write cache staat? Dat kan alleen met een vorm van dirty checking. Ik laat me graag corrigeren, maar volgens mij zegt het JMM niet zoveel over hoe caches moeten reageren op code die niet binnen een memory barrier wordt uitgevoerd. Als je dit soort grappen uithaalt is je code "vogelvrij", zoals ik al met een ander voorbeeld liet zien. Waarom dan ook niet wat maffe cache effecten die de write weggooien?
Het is natuurlijk onzin dat de caches zich niet zo zouden kunnen gedragen. Dat betekent namelijk dat elke write gevolgd door een read garbage op kan leveren (of je nou in multithreaded omgevingen werkt of niet) en wordt je applicatie zo instabiel als wat. Een CPU die zo werkt heeft dus continu flushes nodig, en dan zal de compiler die beter ook maar genereren.
Ik snap niet hoe je erbij komt dat een cache altijd geflushed zou moeten worden? Met een cache zonder dirty checking zou in een niet multi-threaded programma zonder synchronisatie code niks mis gaan, immers wordt dan gewoon de laatst geschreven waarde gepakt (in die omgeving is er dan geen aparte read- en write cache). Als je niet consequent synchronisatie toepast dan heb je gewoon geen garanties meer. Een cache zonder dirty checking die volgens de JMM omschrijving handmatig wordt geflushed en gepushed werkt wel volgens de semantics van JMM.

Ik weet dat het belachelijk klinkt op bijvoorbeeld een Intel processor, maar JMM is bedoeld voor veel meer architecturen waarvan ik wel weet dat ze veel minder robuust met dit soort issues omgaan ten behoeve van performance. Dit is een theoretische discussie en we mogen dus alleen uitgaan van de garanties die de specs bieden.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

misfire schreef op woensdag 13 september 2006 @ 14:45:
Wat ik bedoel en waar jij het ook mee eens bent is dat de write cache dus niet altijd voorrang krijgt.
Dit snap ik even niet. In welke situatie zou ie nou precies geen voorrang krijgen? Voorrang waarop?
Maar hoe weet die ene thread dan dat hij 10 moet inlezen en niet die 20 vast moet houden die nog in de write cache staat?
Dat weet hij niet, en dus zal hij de 20 inlezen. Wat ik suggereerde is dat de write cache geflushed (+ geinvalideerd) kan worden voor hij de waarde output. Bij een flush/invalidate zal hij voor de volgende read van x dus mogelijk 10 lezen, omdat dat in main mem is gezet na de flush van de 20. Daar is helemaal geen dirty checking voor nodig.
Wat je ook niet moet vergeten is dat als beide threads op verschillende CPUs gescheduled worden, maar dat thread2 na de write van 20 weer op de ene gescheduled wordt, er dus nooit een 20 in z'n cache kan staan - het is immers een fysieke andere CPU waar de code ineens op draait, met z'n eigen cache. Om dit fatsoenlijk te laten werken moet het OS de caches wel geflusht en geinvalidate hebben.
Ik snap niet hoe je erbij komt dat een cache altijd geflushed zou moeten worden?
Dat zeg ik helemaal niet, waar staat dat precies? Ik zeg dat de cache mogelijk geflushed is, en als dat zo is dan zie je de 20 niet meer in je lokale cache, maar lees je de 10 uit main mem.
Dit is een theoretische discussie en we mogen dus alleen uitgaan van de garanties die de specs bieden.
Duh, ik heb me in geen moment vastgepind op een specifieke CPU. Op intel gaat het niet eens op omdat die volgens mij een gesharede L2 cache hebben :)

Overigens ging het erom of je wel of niet een 0 zou kunnen krijgen, niet of je al dan niet een 10 zou kunnen zien. En ik zeg dat dat niet kan, omdat die write van 20 nooit gecanceled kan worden, hij kan slechts overridden worden door een write ná de write van 20 (door een andere thread), en de enige write die na die van 20 kan optreden is die van 10.

[ Voor 8% gewijzigd door .oisyn op 13-09-2006 17:21 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Confusion
  • Registratie: April 2001
  • Laatst online: 01-03-2024

Confusion

Fallen from grace

Edit: * Confusion zag een paar reacties over het hoofd...

[ Voor 91% gewijzigd door Confusion op 13-09-2006 18:09 ]

Wie trösten wir uns, die Mörder aller Mörder?


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
OK nu snap ik denk ik waar het verschil in denken zit. Jij gaat ervan uit dat als de cache invalidated wordt bij het begin van het synchronized blok alle writes eerst worden weggeschreven naar main memory. Op zich zou mij dat ook niet onlogisch lijken, maar dat zie ik nergens keihard staan (en volgens mij is Alarmnummer daar ook naar op zoek). Het enige wat wordt gegarandeerd bij het begin van een synchronized blok is dat je waardes uit main memory leest. Op het einde van het synchronized blok wordt wél een garantie van een flush gegeven en weet je zeker dat vanaf dat moment alle writes visible zijn voor andere threads. Die garanties zijn op zichzelf voldoende zolang je maar correct met synchronized omgaat, maar als je dat niet goed doet dan ben ik met je eens dat dit voor rare dingen zou kunnen zorgen.

Ik heb nog eens door de specs gekeken en hier wordt wel gesproken over intra-thread semantics, dwz dat variabelen in principe altijd wel zich lokaal moeten gedragen zoals ze binnen de thread worden behandeld. Misschien dat deze clausule betekent dat je inderdaad de garantie hebt dat de write naar altijd 20 zichtbaar is aan het begin van de synchronized blok omdat dit een intra-thread action zou zijn. Zoals ik het nu lees lijken deze regels echter meer op uitzonderingen voor lokale variabelen, niet voor shared variables als bijvoorbeeld member fields.

Ik moet wel zeggen dat ik het lastig vind om sommige definities te doorgronden, dus ik kan er best helemaal naast zitten. :) Als iemand me kan aanwijzen dat er ergens min of meer duidelijk staat dat writes wel degelijk bij het begin van een synchronized worden geflushed dan geef ik me graag gewonnen. Tot die tijd hou ik even koppig vol dat dit niet gegarandeerd wordt. >:) ;)

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

misfire schreef op donderdag 14 september 2006 @ 11:34:
Jij gaat ervan uit dat als de cache invalidated wordt bij het begin van het synchronized blok alle writes eerst worden weggeschreven naar main memory. Op zich zou mij dat ook niet onlogisch lijken, maar dat zie ik nergens keihard staan (en volgens mij is Alarmnummer daar ook naar op zoek).
Klopt, ik maakte idd die aanname, maar niet per se door het synchronized block - tussen de write en de read zou een flush+invalidate kunnen optreden, al is het alleen maar omdat daar bijvoorbeeld een thread switch plaats vindt. Maar nooit alleen een invalidate, omdat je anders geen enkele garanties meer kunt geven over elke willekeure applicatie. Dit kan dan al fout zijn:
Java:
1
2
3
4
5
6
void foo()
{
    int x = 3;
    // op dit moment kan, om onbekende redenen, een invalidate gedaan worden die de x=3 canceled
    System.out.println("x = " + x);
}

Dat is natuurlijk van de zotte, en daarom maak ik die aanname :)

Dat een flush+invalidate optreedt neem ik niet aan, maar tegelijkertijd staat ook niet vast dat het niet gebeurt (de VM doet misschien niets, maar het OS kan vanalles doen in z'n scheduler), dus je kunt ook niet aannemen dat je bij de output altijd de waarde 20 weer uit de cache haalt in het originele voorbeeld.

[ Voor 19% gewijzigd door .oisyn op 14-09-2006 13:47 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 13-02 14:38
@misfire: de specs eisen dat statements binnen een thread geordend zijn. Dat heeft met caches weinig te maken. Als statement A ("x = 20") voor statement B ("print(x)") staat, dan moet de compiler ervoor zorgen dat de nieuwe waarde van x geprint wordt. De compiler mag dus geen code genereren die er voor zorgt dat een andere waarde van x geladen wordt, tenzij er natuurlijk een andere thread is die 'tussendoor' x overschrijft.

Op die manier kun je niet x=0 zien, want die operatie kan nooit tussen statement A en statement B uitgevoerd worden, aangezien het geheugen op 0 wordt geïnitialiseerd voordat de Java compiler er iets mee kan doen.

Het klopt dat bij het begin van een synchronized block de write cache niet geflushed hoeft te worden, maar dat maakt hier niet voor uit.

[ Voor 9% gewijzigd door Soultaker op 14-09-2006 13:46 ]


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
@.oisyn: Ik heb het specifiek over het gedrag van het synchronized blok. Het gedrag voor als de thread wordt verhuist naar een andere fysieke processor oid is duidelijk. Daarvan wordt in het JMM wel degelijk gesteld dat dit as-if-serial moet reageren, anders zouden de rapen inderdaad gaar zijn. Wat ik stel is dat het theoretisch mogelijk is volgens de JMM dat bij het begin van een synchonized alleen een invalidate plaats kan vinden, en geen flush. Dat is wel even wat anders.

@Soultaker: statement A staat wel voor B, maar je moet niet vergeten dat het synchronized blok er tussen in staat. :) Het synchronized blok is juist een expliciete manier om aan te geven: "hier verwacht ik dat andere threads iets met x gedaan hebben", en voor de compiler/runtime is dat juist een signaal om iets met x te gaan doen om er zeker van te zijn dat writes op x van andere threads die ook synchronized waren zichtbaar zijn. De compiler/runtime gaat er van uit dat je je aan de regels houdt. Als je dat niet doet (zoals in dit voorbeeld) dan kun je gewoon raar gedrag zien. De vraag is: welk raar gedrag?

Het volgende stukje is uit de JMM FAQ waarin ik wat dingen bold heb aangemerkt:
But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.
Ook in de officiële specs staat het zo beschreven. Het statement "x = 20;" lijkt gewoon vogelvrij. Had ie maar synchronized moeten zijn.

Laten we het er maar op houden dat we het allemaal niet zeker weten wat er nu écht gedefinieerd staat. :) Misschien dat degene die dit onderwerp tot hobby heeft verheven er nog uitsluitsel over kan geven. :*)

[ Voor 4% gewijzigd door misfire op 14-09-2006 15:22 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Dus in dat opzicht kan dit onverwachte resultaten opleveren?
Java:
1
2
3
4
5
6
7
8
void foo()
{
    int x = 3;
    synchronized(new Object)
    {
        System.out.println("x = " + x);
    }
}

Dat wil er bij mij eerlijk gezegd gewoonweg niet in :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Dat voorbeeld compileert niet eens. :P Maar zoals ik het lees: ja, dat zou best wel eens kunnen. Het is ook ongeldig gebruik van synchronized. Dat dit in de praktijk meestal wel goed zal gaan geloof ik meteen. Maar de specs lijken niet af te dwingen dat dit werkt.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Vanwege de weggelaten haakjes achter new Object? Wat stom, ik dacht dat dat in Java ook wel zou mogen :P. De class eromheen moet je er natuurlijk wel even bij denken, maar dat is triviaal

Waarom is dit ongeldig gebruik van synchronized? Goed, de monitor van het object is geen enkele andere thread te verkrijgen, maar of ik het nou eerst in een globale var plaats en vandaaruit de synchronized toepas of meteen het resultaat van de new expression in het synchronized statement gebruik, het maakt geen verschil.

[ Voor 66% gewijzigd door .oisyn op 14-09-2006 16:19 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Nick_S
  • Registratie: Juni 2003
  • Laatst online: 06:32

Nick_S

++?????++ Out of Cheese Error

Zoals ik het begrijp (correct me if I'm wrong) gaat een synchronized block kijken of er een lock op een Object instantie zit. Zodra je voor elke lock per aanroep een nieuw Object maakt, zal daar dus nooit een lock op zitten en zal er dus ook geen syncronisatie plaatsvinden.

'Nae King! Nae quin! Nae Laird! Nae master! We willna' be fooled agin!'


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Your point being...?
Het gaat niet om het al dan niet synchroniseren ergens mee (nee, natuurlijk, als niemand de monitor heeft dan hoeft de huidige thread ook niet te wachten, maar dat is het hele punt niet en heeft ook niets te maken met het al dan niet geldig zijn van een dergelijke statement), het gaat erom dat de cache invalidate sideeffects die inherent zijn aan de synchronized er mogelijk voor kunnen zorgen dat die write van 3 aan x gecanceled wordt en dus nooit in main mem terecht komt. Waar ik dus niet in geloof, maar waarin de standaard tot op heden nog onduidelijk is gebleken.

[ Voor 18% gewijzigd door .oisyn op 14-09-2006 16:47 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • misfire
  • Registratie: Maart 2001
  • Laatst online: 12-10-2024
Ja die haakjes ontbreken en dat mag echt niet in Java, maar ik snap het wel hoor. :) Waar ik in eerste instantie overheen keek was dat je x nu ook als lokale variabele hebt gedefinieerd. Dit voorbeeld is in de JMM specs wel benoemd in de beschrijving over intra-thread actions, en hierdoor zal het synchronized blok geen effect hebben. Een slimme optimizer zou dit blok overigens ook kunnen weg optimizen met een escape analysis op het lock object.

Ik begin er nu ook een beetje debiel van te worden en ik ben het met je eens dat het wel tot maffe situaties kan leiden. Ik moet ook bekennen dat ik niet bij alle teksten van de spec volledig begrijp wat ze nu bedoelen. Dus wie weet valt het allemaal mee en ligt het gewoon aan mij. :) Ik ben wel benieuwd of iemand met uitsluitsel over dit haarkloverij-onderwerp kan komen!

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
.oisyn schreef op donderdag 14 september 2006 @ 16:45:
Your point being...?
Het gaat niet om het al dan niet synchroniseren ergens mee (nee, natuurlijk, als niemand de monitor heeft dan hoeft de huidige thread ook niet te wachten, maar dat is het hele punt niet en heeft ook niets te maken met het al dan niet geldig zijn van een dergelijke statement), het gaat erom dat de cache invalidate sideeffects die inherent zijn aan de synchronized er mogelijk voor kunnen zorgen dat die write van 3 aan x gecanceled wordt en dus nooit in main mem terecht komt. Waar ik dus niet in geloof, maar waarin de standaard tot op heden nog onduidelijk is gebleken.
Het verliezen van writes is zo ver ik begrepen heb niet mogelijk. 10 en 20 zijn de correcte antwoorden. Ik moet het nog even goed laten bezinken (wil er dit weekend mee gaan spelen) en dan hoop ik iets meer informatie te kunnen geven.
Pagina: 1