Volatile Thread Variablenzugriff

Hallo,
ich habe mir hier ein kleines Tutorial zu Threads angesehen.
https://www.dpunkt.de/java/Programmieren_mit_Java/Multithreading/3.html
Hier wird von einem Bankensystem gesprochen, wo mehrere Threads auf ein Konto Zugreifen und etwas auf- und abbuchen möchten.
Wenn also 100 Mark auf dem Konto sind, der erste Thread fängt an 20 Mark abzubuchen und der zweite hat, bevor der erste damit fertig ist noch die 100 gesehen und will auch 30 Abbuchen.
Am Ende hat der erste 20 abgebucht und der zweite, der noch von 100 ausging bucht seine 30 ab und es sind am Ende 70.
D.h. es wurden 20 Mark einfach so erschaffen, weil die Threads sich überlagert haben.

Ich bin auf das Stichwort volantile gestoßen, welches man vor den Wert für den Kontostand schreiben kann.
Habe damit nicht selbst programmiert, aber ist es richtig, dass dadurch das Problem gelöst werden kann?
So wie ich das verstanden habe ermöglicht das Wort, dass erst mit dem ersten fertig gerechnet wird, bevor der zweite Thread anfängt.
Kann das jemand bestätigen und im Fall, dass ich mich total irre das ganze richtigstellen?

Gruß
Lukas

Das stimmt leider nicht. Du müsstest dennoch mit synchronized bzw. Locks arbeiten. Volatile reicht nur aus, falls

Regel 1:
der zu schreibende neue Wert der Variablen unabhängig vom gegenwärtigen Wert ist, und
Regel 2:
die Variable unabhängig von anderen Variablen ist.

Siehe: AngelikaLanger.com - Regeln für die Verwendung von volatile - Angelika Langer Training/Consulting

erstes Suchergebnis zu ‚volantile java‘…
http:/ /w ww.zdnet.de/39198058/java-daten zugriffe-mit-dem-schluesselwort-i-volatile-i-synchronisieren/

ich mag ihn gar nicht recht verlinken für weitere Verbreitung, Leerzeichen am Anfang und zwischen daten + zugriffe eingefügt,

darin wird volatile wirklich mit synchronized gleichgesetzt,
falsch, wie auch Kommentare darunter schreiben (wenn auch erst 2013, Artikel von 2008…)

Dieser Artikel vermittelt den Eindruck, dass volatile ähnlich wie eine Absicherung mit synchronized zur Synchronisierung eingesetzt werden kann. Das stimmt nicht!

Wenn zwei Threads gleichzeitig eine als volatile markierte Variable bearbeiten, kann es zum Beispiel passieren, dass einer die Änderungen des anderen überschreibt.

korrekt in Link 2, wie schon genant AngelikaLanger… :wink:


Wiki:

Im Gegensatz dazu werden bei Java und C# Variablen mit volatile gekennzeichnet, auf die von verschiedenen Threads aus zugegriffen wird, um die Sichtbarkeit zu synchronisieren.[3] Mit volatile gekennzeichnete Variablen werden in Java nicht zwischengespeichert (z. B. in Registern). Dadurch wird sichergestellt, dass auch bei Zugriff von mehreren Threads der richtige Wert gelesen wird. Volatile sorgt in Java nicht für gegenseitigen Ausschluss und ersetzt somit nicht Synchronisationsmechanismen (z. B. Locks oder Semaphoren).[4]

volatile bewirkt hier demnach hauptsächlich, dass nicht der zweite Thread noch die 100 liest, x sec. nachdem der erste Thread schon seine 80 zurückgeschrieben hat,
weil die Variable je Thread irgendwo gecacht wird
(* sollte man eigentlich als Standard annehmen, vielleicht verstehe ich es auch nur falsch, siehe Anmerkung unten),

aber wie im Wiki mehr oder weniger schon steht, ist dein Beispiel damit nicht abgedeckt,
jeder darf munter lesen und vor allem schreiben wann er will, nur echte Synchronisation hilft hier:

jeder Thread kann die Variable jederzeit lesen, auch wenn jemand anders sie gerade bearbeitet, evtl. schon selber vorsorglich rechnen,
erst wenn ein Thread den exklusiven Zugriff bekommt geht es für ihn richtig los:
nochmal den Wert lesen, nicht darauf verlassen was vorher mal gelesen, vielleicht inzwischen Kontostand 0, Pech gehabt,
aber wenn noch was auf dem Konto ist, dann für sich beanspruchen, neuen Wert berechnen, zurückschreiben, Zugriff freigeben

wobei auch Synchronisation zumindest per synchronized nur funktioniert nur wenn alle anderen Teilnehmer freiwillig mitmachen, auch synchronized verwenden,
die Variable an sich erfährt davon keinen technischen Schutz, böser oder fehlerhafter Code kann immer noch beliebig herumpfuschen

wichtige Daten idealerweise in einem Objekt mit Zugriff nur über synchronized Methoden kapseln, dann Fehl-Nutzung kaum mehr möglich


(*) dass jeder Thread jeden Wert im Arbeitsspeicher zu jedem Zeitpunkt korrekt liest sollte man eigentlich als Standard annehmen…,

ich persönlich mache zwar generell nicht viel mit mehreren Threads, aber es kommt doch vor,
und kann mich nicht erinnern, jemals irgendwo einen Fehler durch falsch gelesenen Wert zu haben, und das ohne volatile…,
wäre recht übel, besser freilich gleich mit volatile auf Nummer sicher zu gehen,

vielleicht aber auch für Sonderumgebungen/ bestimmte JVM gedacht?..

für den Fall mit dem einzelnen boolean, wo volatile reicht, reicht mir normalerweise Verzicht auf Synchronisation:
wer noch Wert ‚false‘ liest macht eben weiter, wenn aber ‚true‘ erfolgte, dann ‚true‘ gelesen und Ende, fertig,
(edit: freilich nur wenn es nicht schlimm ist falls kurz nach false noch Zugriff erfolgt, nicht schon Ressourcen geschlossen sind usw.)

Do you ever use the volatile keyword in Java? - Stack Overflow
geht ja auch voll auf diesen boolean-Fall ab, nicht darauf dass überhaupt Variablen von anderen Threads zu lesen sind

wie sieht es normalerweise aus, etwa wenn nur ein Thread seine eigene einzelne Variable beschreibt,
und andere nur lesen, keine Synchronisation erforderlich,

braucht es volatile, damit andere den neuen Wert lesen können?
gibt es Testprogramme, wo man ein Fehl-Lesen hinbekommt?

oder verstehe ich das in der Lese-Hinsicht falsch?
sorry, falls Forum-Thread für diese Frage nicht gedacht war :wink:

Bezüglich des Testprogramms: Da hängt es natürlich von ungefähr 120153 Faktoren ab, ob man den Effekt sieht. Man könnte sich http://www.akkadia.org/drepper/cpumemory.pdf (hatte ich eh gerade offen :D) oder Intel® 64 and IA-32 Architectures Developer’s Manual durchlesen, um eine Ahnung zu haben, wie das auf einer Architecktur mit einem OS ist (unabhängig davon, wie sehr dieses Verhalten durch die JVM-Spec “gefiltert” sichtbar wird).

Aber schon bei so einem brute-force-Ding wie diesem hier

public class VolatileTest {

	private static boolean flag = false;

	public static void main(String[] args) {

		Thread t0 = new Thread(new Runnable() {
			public void run() {
				while (true) {
					if (flag) {
						System.out.println("Got it!");
						return;
					}
				}
			}
		});
		t0.start();

		Thread t1 = new Thread(new Runnable() {
			public void run() {
				while (true) {
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
						return;
					}
					System.out.println("Setting");
					flag = true;
				}
			}
		});
		t1.start();

	}
}

gibt er bei mir “Setting, Setting, Setting…” aus, ohne jemals “Got it!” auszugeben - außer, wenn man das flag volatile macht - dann sieht er es sofort.

wow, bei mir kein Unterschied,
bir dir auch mit einem Programm a la

noch eins
[spoiler]```class VolatileTest {
private static boolean flag = false;

public static void main(String[] args) {

    Thread t0 = new Thread(new Runnable()    {
            public void run()   {
                while (true)     {
                    sleep(480);                        
                    System.out.println("Setting is: "+flag);
                }
            }
        });
    t0.start();

    Thread t1 = new Thread(new Runnable()      {
            public void run()          {
                while (true)         {
                    sleep(500);
                    flag = true;
                    System.out.println("set: " + flag);
                }
            }
        });
    t1.start();

}

static void sleep(int x) {
    try   {
        Thread.sleep(x);
    }
    catch (InterruptedException e)    {     }
}

}


die Ausgabe `Setting is: false` NACH `set: true` vorkommend?
irgendwas wichtiges zur Umgebung anzumerken, abweichend vom Standard 0815-Windoof-Rechner, Standard-Java (32 Bit), Standard-Eclipse?
(edit: zu 64 Bit siehe unten)

ist das irgendwo mal so direkt angesprochen?
findest du es nicht merkwürdig wenn in Threads wie
[Do you ever use the volatile keyword in Java? - Stack Overflow](http://stackoverflow.com/questions/106591/do-you-ever-use-the-volatile-keyword-in-java)
die atomare Synchronisation angesprochen wird, nicht die simple Aktualität der Sichtbarkeit einer Variablen in anderen Threads?

------

wie sieht es aus wenn man normale Synchronisierung auf ein beliebiges zusätzliches statisches Monitor-Objekt hinzufügt,
oder auf das Klassenobjekt synchronisiert ,
oder im nicht statischen Fall verbreitet und von der Sprache aufgemuntert auf das Objekt an sich `public synchronized void setFlag() { .. } ` usw.

diese Synchronisation hat ja strengenommen mit der boolean-Variablen nichts zu tun, muss die boolean-Variable dann trotzdem volatile definiert werden?
müsste das Monitor-Objekt nicht auch volatile sein, soweit es machbar ist 
(bei `public synchronized void set(..) { .. } ` ja gar nicht möglich anzugeben)

oder macht synchronized das selber, hinsichtlich des monitor-Objektes ja noch halbwegs vorstellbar, 
aber werden auch alle Variablen, die wer weiß wo definiert innerhalb eines synchronized-Blocks angesprochen werden, temporär oder dauerhaft zu volatile?!

edit:


> Daneben hat das Anfordern und Freigeben von Locks Speichereffekte: das Anfordern eines Lock löst einen Refresh des thread-spezifischen Caches aus, das Freigeben löst einen Flush aus.  Dadurch werden Änderungen, die ein Thread unter dem Schutz eines Locks im Speicher gemacht hat, anderen Threads, die dasselbe Lock verwenden, sichtbar. 


[AngelikaLanger.com - Regeln für die Verwendung von volatile - Angelika Langer Training/Consulting](http://www.angelikalanger.com/Articles/EffectiveJava/42.JMM-volatileIdioms/42.JMM-volatileIdioms.html)
hmm..


-------

edit: 64 Bit habe ich seit kurzem auch installiert, da bekomme ich nun auch das Nicht-Got-it, dann kann ich den Rest ja selber testen ;)

edit: sieht aus als wird aus deinem Programm das if in der ersten Schleife wegoptimiert,
wozu besteht da der Anlass, die statische Variable ist doch nicht final..

viele einzelne Änderung in der ersten Schleife, 
etwa ein sleep hinzu, eine Ausgabe des Standes,
oder das Speichern des flag-Standes in einer statischen String-Variablen, auch ohne Ausgabe irgendwo,
oder ein
```if (Math.random() > 4)  {
  flag = true;
}

vor der flag-Abfrage
oder if (Math.random() < 4 || flag) ( spätes edit: soll natürlich && sein, damit gehts auch)
oder freilich auch Nutzung von irgendeinem synchronized,
und schon gehts

Verzögerung beim Lesen kann ich so wiederum nicht feststellen, die weiteren Tests entfallen wohl,
nur ein Wegoptimieren einer Code-Zeile ohne rechten Anlass, macht die 64 Bit-Version ganz schön suspekt,

volatile nur um solche Fehler zu vermeiden?..
nutzt du volatile bei vielen Variablen?
was ist wenn ein Thread potentiell riesige Datenstrukturen wie Hibernate-Objekte Person & Co. durchwandern kann, alle Attribute volatile?!

Gerade habe ich nur wenig Zeit, daher habe ich mir dieses eine Statement herausgepickt, um darauf einzugehen.
Das if darf vom Compiler wegoptimiert werden, weil er weiß, dass sich das Flag im ersten Thread gemäß den Vorgaben vom Java Memory Model niemals ändert. Ohne das volatile muss das Flag nicht aktualisiert werden und daher greift die Optimierung.

Eine Synchronisation einer Methode sorgt für eine Speicherbarriere. Das JMM garantiert dann gewisse Bedingungen über die Sichtbarkeit von Speicherveränderungen (einschlägiges Stichwort: happens-before-Beziehung). Dadurch werden Speicheränderungen, die vor der Synchronisation gemacht wurden, in anderen Threads aktualisiert.

Das alles korrekt zu beschreiben füllt Bücher (Brian Goetz’ „Java Concurrency in Practice“ sei empfohlen).

Zu den potentiell riesigen Datenstrukturen auch eine Anmerkung. Entscheidend sind Faktoren wie:

  • wann wurde der Thread gestartet, der die Daten sehen muss
  • sind die Datenstrukturen veränderbar („immutable“ ist der Beste Mechanismus um Sichtbarkeitsprobleme zu vermeiden und dabei die größte Performance zu gewinnen)

[QUOTE=cmrudolph]
Zu den potentiell riesigen Datenstrukturen auch eine Anmerkung. Entscheidend sind Faktoren wie:

  • wann wurde der Thread gestartet, der die Daten sehen muss
  • sind die Datenstrukturen veränderbar („immutable“ ist der Beste Mechanismus um Sichtbarkeitsprobleme zu vermeiden und dabei die größte Performance zu gewinnen)
  • …[/QUOTE]

wenn immutable, und auch Nachfolger der Objekte nach Änderungen nicht zu sehen, dann braucht man über die ganze Synchronisation ja nicht zu sprechen :wink:

wenn die Nachfolger irgendwo abrufbar sind (außer neu aus DB geladen), dann ist ja wieder was mutable dabei,
aber Problem vielleicht reduziert nur eine Programmstelle, eine Liste:
solange die aktuell ist, wird sie schon die richtigen Objekte enthalten, falls es nur eine Objektversion gibt wegen immutable


Beispiele für sowas sind immer fraglich,
aber hier etwa grob beim Bankensystem geblieben, fiktiv bleiben geladene Konten bei Normallast eine Stunde im System,
ein Überwachungsthread schaut alle halbe Stunde die Liste durch, neben den Konto-Objekten auch Zusatzinfos zur Art des Zugriffs, Änderung oder nicht, baut Statistik,
ausnahmsweise mal ohne synchronized allgemein oder auf jedes dieser beteiligten Objekte einzeln…

aber anscheinend reicht ja ein einzelnes synchronized auf irgendwas um zu einem Zeitpunkt den kompletten ‚Arbeitsspeicher‘ dieses Threads zu aktualisieren,
was immer das bedeuten mag, hat der potentiell tausende Objekte in lokalen Cache?
mit vielen volatile oder auch vielen synchronized auf einzelne Objekte muss man sich dann ja nicht aufhalten (hinsichtlich Sichtbarkeit von Änderungen in letzter halber Stunde)…


wie gesagt ist aber auch beim Beispiel von Marco13 die Sichtbarkeit auf die Millisekunde anscheinend vorhanden,
solange nur der Code nicht wegoptimiert wird, was wohl doch normalerweise in Tests auffällt,

gibts ein Beispiel mit nicht wegoptimierten Code und verzögerter/ gar nicht aktualisierter Sichtbarkeit?

[OT]

Aber die 20 Mark sind doch sowieso nichts mehr wert…
[/OT]

Tag der vielen Postings:

sogar wenn die statische boolean-Variable public ist…,

ist anscheinend eher die JVM wie ja destöfteren zu hören,
der Compiler hätte hoffentlich die Freundlichkeit, beim einfach zu erkennenden Schreibzugriff auf die Variable in derselben Klasse, eine Warnung auszugeben,

schaffen das irgendwelche Sonder-Tools die so vieles erkennen, volatile-Hilfe?


kann man eigentlich einen solchen Optimierer, einfach verbieten, Zeilen zu löschen?
kann man anscheinend:
java - how to make sure no jvm and compiler optimization occurs - Stack Overflow
VM-Argument: -Djava.compiler=NONE

dann läuft es auch wieder ohne volatile ordentlich durch und die Frage bleibt:
gibt es Testprogramme wo Auswirkung zur Laufzeit zu bemerken? :wink:

aber komplette Optimierung aus auch nicht schön, anderer Code vielleicht langsamer, ok, volatile-Einsatz sicher bessere Lösung…

Ich hatte es “brute-force-Ding” genannt, weil es wirklich nur schnell hingeschrieben war. Ich hatte es nicht auf 32bit getestet.

(Und BTW: Was dich zu der Aussage veranlasst, dass das setzen “wegoptimiert” wurde, weiß ich nicht - im bytecode sollte das nicht passieren (und passiert auch beim ersten Draufschauen nicht, könnte aber auch was übersehen haben), und den JIT-Hotspot-Disassembler habe ich jetzt noch nicht angeworfen. Trotzdem: )

Er DARF das natürlich optimieren, solange alle constraints erfüllt sind, die laut Spec erfüllt sein müssen.

Einige Punkte, die du nanntest, scheinen Fragen und Einsichten zu vermischen. Im speziellen sind deine Einsichten (auf Basis des AngelikaLanger-Links) eben das, was schnell an die Oberfläche treibt, wenn man sich mit dem JMM beschäftigt. Kurz gesagt: Ein “synchronized” macht ein “flush”. Deswegen wird der ursprünglich beobachtete Effekt leicht kaputt gemacht, wenn man ein unbedachtes “System.out.println” oder “Math.random” dort einstreut, weil gerade solche “globalen” Sachen eben gerne mal ein “synchonized” beinhalten.

Es gibt AFAIK schon einige Tools. Auf der letzten Parallel wurde in parallel 2016 - Softwarekonferenz für Parallel Programming, Concurrency und High-Performance Computing | Heidelberg, 6.-8. April 2016 :: Parallele Code Smells: Eine Sammlung für .NET auch von “Parallelen Code Smells” geredet, und Websuchen liefern schnell Papers und Tools um bestimmte “Muster” zu erkennen (d.h. irgendwelche suspekten, unsynchronisierten Zugriffe). Aber dass das schwierig ist, sollte klar sein.

na, wenn du Zeit hast, schaue doch noch mal nach ob es nicht allein die Wegoptimierung auch bei dir ist
bei etwa Änderung auf if (Math.random() < 4 && flag) wird sicher niemand anders synchronisieren,

Bytecode anscheinend nicht betroffen

bei meinem zweiten Programm mögen dann freilich die Ausgaben helfen, richtig

Nochmal: Wie hast du die Wegoptimierung festgestellt? Mit dem JIT-Hotspot-Disassembler? De spuckt für

public class VolatileTest
{
    private static boolean flag = false;

    static class RunnableA implements Runnable
    {
        @Override
        public void run()
        {
            while (true)
            {
                checkFlag();
            }
        }
    }

    static void checkFlag()
    {
        if (flag)
        {
            System.out.println("Got it!");
        }
    }

    static class RunnableB implements Runnable
    {
        @Override
        public void run()
        {
            while (true)
            {
                setFlag();
            }
        }
    }

    private static void setFlag()
    {
        try
        {
            Thread.sleep(500);
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
            return;
        }
        System.out.println("Setting");
        flag = true;
    }

    public static void main(String[] args)
    {

        Thread t0 = new Thread(new RunnableA());
        t0.start();

        Thread t1 = new Thread(new RunnableB());
        t1.start();

    }
}

als letzte Version von “checkFlag” zwar das hier aus,


Decoding compiled method 0x000000000286f550:
Code:
<writer thread='4804'/>
@ 11  java/io/PrintStream::println (not loaded)   not inlineable
<writer thread='4828'/>
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x00000000244b0460} 'checkFlag' '()V' in 'VolatileTest'
  #           [sp+0x20]  (sp of caller)
  0x000000000286f680: mov    %eax,-0x6000(%rsp)
  0x000000000286f687: push   %rbp
  0x000000000286f688: sub    $0x10,%rsp         ;*synchronization entry
                                                ; - VolatileTest::checkFlag@-1 (line 19)

  0x000000000286f68c: movabs $0x71afebdd0,%r10  ;   {oop(a 'java/lang/Class' = 'VolatileTest')}
  0x000000000286f696: movzbl 0x68(%r10),%r11d   ;*getstatic flag
                                                ; - VolatileTest::checkFlag@0 (line 19)

  0x000000000286f69b: test   %r11d,%r11d
  0x000000000286f69e: jne    0x000000000286f6ac  ;*ifeq
                                                ; - VolatileTest::checkFlag@3 (line 19)

  0x000000000286f6a0: add    $0x10,%rsp
  0x000000000286f6a4: pop    %rbp
  0x000000000286f6a5: test   %eax,-0x41f6ab(%rip)        # 0x0000000002450000
                                                ;   {poll_return}
  0x000000000286f6ab: retq   
  0x000000000286f6ac: mov    $0xffffff65,%edx
  0x000000000286f6b1: mov    %r11d,%ebp
  0x000000000286f6b4: data32 xchg %ax,%ax
  0x000000000286f6b7: callq  0x00000000027a57a0  ; OopMap{off=60}
                                                ;*ifeq
                                                ; - VolatileTest::checkFlag@3 (line 19)
                                                ;   {runtime_call}
  0x000000000286f6bc: int3                      ;*ifeq
                                                ; - VolatileTest::checkFlag@3 (line 19)

  0x000000000286f6bd: hlt    
  0x000000000286f6be: hlt    
  0x000000000286f6bf: hlt    
[Exception Handler]
[Stub Code]
  0x000000000286f6c0: jmpq   0x00000000027cc5e0  ;   {no_reloc}
[Deopt Handler Code]
  0x000000000286f6c5: callq  0x000000000286f6ca
  0x000000000286f6ca: subq   $0x5,(%rsp)
  0x000000000286f6cf: jmpq   0x00000000027a7200  ;   {runtime_call}
  0x000000000286f6d4: hlt    
  0x000000000286f6d5: hlt    
  0x000000000286f6d6: hlt    
  0x000000000286f6d7: hlt    

aber das sagt ja nicht sooo viel aus…

-Djava.compiler=NONE
und Programm läuft

OT: Da kannte ich nur “-Xint”, und ```(http://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html) listet auch nichts anderes. Aber wer weiß, was genau er dann macht. Er dürfte sich dann zumindest noch genauso verhalten, aber natürlich wird bei “echter bytecode-interpretation” die Schicht so dick, dass jeglicher Hardwarenaher Effekt verloren geht…

@SlaterB
Um mal alle bisherigen Posts zu überspringen und zu einer deiner Fragen zu kommen, es ist unglaublich scher ein “volatile” zu “fangen”.
Denn beim Versuch den Fehler sichtbar zu machen eliminiert man meist den Fehler unbeabsichtigt, denn jede kleinste Änderung kann den Cache veranlassen sich jetzt doch noch kurz die neuesten Informationen zu holen.
Z.B. wenn die CPU gerade “wartet” oder wenn ein konstanter Thread Wechsel stattfindet.
Praktisch Schrödingers Käfer wenn man so will.

Deshalb sehe ich Multithreading Fehler auch als die größten und nervigsten Fehler. Man kriegt nie eine genaue Exception und die Fehler treten immer sporadisch an unterschiedlichen Stellen in unterschiedlichen Formen auf und sind damit praktisch nie reproduzierbar.

Mit Marco13 s Programm sollte man deshalb höchstens hin und wieder eine Desynchronization der Werte sehen aber konstant ist das Verhalten eigentlich nie.

[ot]Ein typischer Heisenbug :-)[/ot]

Wunderschönen Guten Tag euch allen,

ich danke euch für eure Antworten und freue mich, dass ich eine kleine Diskussion entfachen konnte. Sehr sehr interessant das alles zu lesen. Ich denke das bringt mich bei diesem theoretischen Beispiel und der Wissensakkumulation weiter. :slight_smile:

[OT]@Landei Sorry, das mit der Mark war ein Test, ob irgendwer drauf anspringt. Wusste ich es doch. :wink: Dass die keinen Wert mehr hat ist nicht ganz korrekt, denn man kann sie weiterhin zum Kurs von 1,95583 zum Euro umtauschen. Die Ostmark hingegen wird nicht mehr getauscht.[/OT]

eine Folge dieses ganzen Memory-Ignorierens ist, dass z.B. in GUI-Programmen mit eigenen Thread vs. EDT für paint + Listener der Zugriff auf alle gemeinsamen Variablen synchronisiert werden muss,

ist es richtig, dass theoretisch der EDT das intern oft genug macht wie hier genannt
(im durchgestrichener Text, durchgestrichen wegen etwas anderem, später noch in diesem Posting erwähnt) ?
Java: Concurrency inside ActionListener.actionPerformed - Stack Overflow

falls sich wer angesprochen fühlt, verlasst ihr euch darauf oder alternativ synchronized in jedem Listener der allgemeine Daten für andere Threads ändert
(bei denen nicht die normalen Probleme bestehen die synchronized eh erfordern, wie List.add() usw., sondern etwa boolean, viele volatile auch noch Variante…) ?

die zweite Seite dazu ist, im eigenen Thread alle Änderungen per Synchronisation zur Verfügung zu stellen (deswegen im Link durchgestrichener Text),
damit der EDT diese Änderungen sieht


hier ein Beispielprogramm aus dem Forum:

der Themensteller hat das natürlich überhaupt nicht auf dem Plan, meine Wenigkeit nicht,
und immerhin auch 4 weitere Forum-Nutzer, von denen einige eigenen mehr oder weniger veränderten Code posten, aber niemand verwendet volatile oder synchronized,
was ist davon zu halten?

hier das GamePanel aus dem Quaxli-Spiele-Tutorial
[spoiler]```public class GamePanel extends JPanel implements Runnable, KeyListener, ActionListener{

private static final long	serialVersionUID	= 1L;
JFrame frame;

long delta 		= 0;
long last 		= 0;
long fps 		  = 0;
long gameover = 0;

Heli copter;
Vector<Sprite> actors;

Vector painter;

boolean up;
boolean down;
boolean left;
boolean right;
boolean started;
int speed = 50;

Timer timer;
BufferedImage[] rocket;
BufferedImage[] explosion;
BufferedImage background;

SoundLib soundlib;

public static void main(String[] args){
	new GamePanel(800,600);
}

public GamePanel(int w, int h){
	this.setPreferredSize(new Dimension(w,h));
	this.setBackground(Color.BLUE);
	frame = new JFrame("GameDemo");
	frame.setLocation(100,100);
	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	frame.add(this);
	frame.addKeyListener(this);
	frame.pack();
	frame.setVisible(true);
	

	Thread th = new Thread(this);
	th.start();
}

private void doInitializations() {

	last 	   = System.nanoTime();
	gameover = 0;

	BufferedImage[] heli = loadPics("pics/heli.gif", 4);
	rocket               = loadPics("pics/rocket.gif",8);
	background           = loadPics("pics/background.jpg",1)[0];
explosion            = loadPics("pics/explosion.gif",5);
	
	actors  = new Vector<Sprite>();
	painter = new Vector<Sprite>();
	copter  = new Heli(heli,400,300,100,this);
	actors.add(copter);		
	
	soundlib = new SoundLib();
	soundlib.loadSound("bumm", "sound/boom.wav");
	soundlib.loadSound("rocket", "sound/rocket_start.wav");
	soundlib.loadSound("heli", "sound/heli.wav");
	
	createClouds();
	
	timer = new Timer(3000,this);
	timer.start();
	
	started = false;
}

private void createClouds(){
	
	BufferedImage[] bi = loadPics("pics/cloud.gif", 1);
	
	for(int y=10;y<getHeight();y+=50){
		int x = (int)(Math.random()*getWidth());
		Cloud cloud = new Cloud(bi,x,y,1000,this);
		actors.add(cloud);
	}
	
}

public void createExplosion(int x, int y){
	ListIterator<Sprite> it = actors.listIterator();
	it.add(new Explosion(explosion,x,y,100,this));
	soundlib.playSound("bumm");
}

private void createRocket(){
	
	int x = 0;
	int y = (int)(Math.random()*getHeight());
	int hori = (int)(Math.random()*2);
	
	if(hori==0){
		x = -30;
	}else{
		x = getWidth()+30;
	}
	
	
	Rocket rock = new Rocket(rocket,x,y,100,this);
	if(x<0){
		rock.setHorizontalSpeed(100);
	}else{
		rock.setHorizontalSpeed(-100);
	}
	
	ListIterator<Sprite> it = actors.listIterator();
	it.add(rock);
	soundlib.playSound("rocket");
	
}

@Override
public void run() {

	while(frame.isVisible()){

		computeDelta();
		
		if(isStarted()){
			checkKeys();
			doLogic();
			moveObjects();
      cloneVectors();			
		}
		
		repaint();
		
	  try {
			Thread.sleep(10);
		} catch (InterruptedException e) {}	

	}
	
}

@SuppressWarnings("unchecked")
private void cloneVectors(){
	painter = (Vector<Sprite>) actors.clone();
}


private void moveObjects() {
	
	for(ListIterator<Sprite> it = actors.listIterator();it.hasNext();){
		Sprite r = it.next();
		r.move(delta);
	}
			
}

private void doLogic() {
	
	for(ListIterator<Sprite> it = actors.listIterator();it.hasNext();){
		Sprite r = it.next();
		r.doLogic(delta);
		
		if(r.remove){
			it.remove();
		}
	}
	
	for(int i = 0;i < actors.size();i++){
  for(int n = i+1; n<actors.size(); n++){

     Sprite s1 = actors.elementAt(i);
     Sprite s2 = actors.elementAt(n);
     
     s1.collidedWith(s2);
     
  }
	}
	
	if(copter.remove && gameover==0){
		gameover = System.currentTimeMillis();
	}
	
	if(gameover>0){
		if(System.currentTimeMillis()-gameover>3000){
			stopGame();
		}
	}
	
}

private void startGame(){
	doInitializations();
	setStarted(true);
	soundlib.loopSound("heli");
}

private void stopGame(){
	timer.stop();
	setStarted(false);
	soundlib.stopLoopingSound();
}


private void checkKeys() {
			
	if(up){
		copter.setVerticalSpeed(-speed);
	}
	
	if(down){
		copter.setVerticalSpeed(speed);
	}
	
	if(right){
		copter.setHorizontalSpeed(speed);
	}
	
	if(left){
		copter.setHorizontalSpeed(-speed);
	}
	
	if(!up&&!down){
		copter.setVerticalSpeed(0);
	}
	
	if(!left&&!right){
		copter.setHorizontalSpeed(0);
	}
	
}



private void computeDelta() {

	delta = System.nanoTime() - last;
	last = System.nanoTime();
	fps = ((long) 1e9)/delta;
	
}

@Override
public void paintComponent(Graphics g) {
	super.paintComponent(g);
	
	g.drawImage(background, 0, 0, this);
	
	g.setColor(Color.red);
	g.drawString("FPS: " + Long.toString(fps), 20, 10);

	
	if(!started){
		return;
	}
	
	for (ListIterator<Sprite> it = painter.listIterator(); it.hasNext();) {
		Sprite r = it.next();
		r.drawObjects(g);
	}
	
}


private BufferedImage[] loadPics(String path, int pics){
	
	BufferedImage[] anim = new BufferedImage[pics];
	BufferedImage source = null;
	
	URL pic_url = getClass().getClassLoader().getResource(path);

	try {
		source = ImageIO.read(pic_url);
	} catch (IOException e) {}
	
	for(int x=0;x<pics;x++){
		anim[x] = source.getSubimage(x*source.getWidth()/pics, 0, source.getWidth()/pics, source.getHeight());
	}
	
	return anim;
}


public boolean isStarted() {
	return started;
}

public void setStarted(boolean started) {
	this.started = started;
}

@Override
public void keyPressed(KeyEvent e) {
	
	if(e.getKeyCode()==KeyEvent.VK_UP){
		up = true;
	}

	if(e.getKeyCode()==KeyEvent.VK_DOWN){
		down = true;
	}

	if(e.getKeyCode()==KeyEvent.VK_LEFT){
		left = true;
	}

	if(e.getKeyCode()==KeyEvent.VK_RIGHT){
		right = true;
	}
	
}

@Override
public void keyReleased(KeyEvent e) {
	
	if(e.getKeyCode()==KeyEvent.VK_UP){
		up = false;
	}

	if(e.getKeyCode()==KeyEvent.VK_DOWN){
		down = false;
	}

	if(e.getKeyCode()==KeyEvent.VK_LEFT){
		left = false;
	}

	if(e.getKeyCode()==KeyEvent.VK_RIGHT){
		right = false;
	}
	
	if(e.getKeyCode()==KeyEvent.VK_ENTER){
		if(!isStarted()){
			startGame();
		}
	}
	
	if(e.getKeyCode()==KeyEvent.VK_ESCAPE){
		if(isStarted()){
			stopGame();
		}else{
			frame.dispose();
		}
	}
}

@Override
public void keyTyped(KeyEvent e) {
	
}

@Override
public void actionPerformed(ActionEvent e) {
	if(isStarted() && e.getSource().equals(timer)){
		createRocket();
	}
}

}```[/spoiler]

auch nix synchronisiert…,
funktioniert freilich auch so bestens, solange nur der dämliche JVM-Optimierer keinen Code klaut besteht ja im Grunde keine Gefahr anscheinend,
weiter unten noch mehr Gemecker dazu :wink:


eine Frage dazu noch genauer gestellt, falls wer was sagen möchte:
im GamePanel gibt es

        {```
alternativ die Würfel-Objekte aus dem Kniffel-Programm

werden wirklich die kompletten Objekte in einen lokalen Speicher gelegt und bekommen dann womöglich andere Belegung der internen Attribute wie boolean?
wie sonst..

hilft volatile auf das eigene Attribut frame (im GamePanel)?, 
oder achtet das nur darauf, ob evtl. ein anderes Objekt in das Attribut gelegt wird?

wenn man sich das JFrame in eine lokale Variable holt (etwa vor langer Game-Schleife), was sich ja allgemein als Cache etwas anbietet, 
dann ist das Attribut mit volatile oder nicht eh ausgeschaltet..

ohne synchronized ginge also gar nichts, wie soll man das aber bei einer Schleifenbedingung `while (frame.isVisible())`formulieren?
außerhalb der Schleife wäre nur ein großes synchronized über gesamte Zeit, 
innerhalb der Schleife, dann nicht synchronisiert vor Prüfen der Bedingung, obwohl vielleicht am Ende des letzten Schleifendurchlaufs.. in dieser Konstellation hier

braucht es idealerweise eine Hilfsmethode, die synchronisiert und `while (checkVisible())` ?
das sind Auswüchse.., nun ja

---------

bei diesem Thema hier
[java - While loop not ending when flag changed in different thread - Stack Overflow](http://stackoverflow.com/questions/11595868/while-loop-not-ending-when-flag-changed-in-different-thread)
ein ähnliches Problem wie Programm von Marco13 hier und grün-Antwort wiederum

> You need to declare the flag volatile, otherwise the compiler can optimize your code and skip the reads of the flag.


was ist das nur für eine Optimierung, die denkbar kompliziert herausfindet, was alles in einem Thread passieren kann,
dabei offensichtlich auch sieht, dass das Flag durchaus auf true gesetzt wird, aber keine Rückmeldung an User für wichtig erachtet..

den JVM-Optimierer ausstellen ist keine Option, aber kann man nicht einzeln sagen dass bitte auf Verzicht von Code verzichtet werden soll?!
wenn er verzichtbar wäre, dann wäre er ja auch nicht geschrieben worden,
und wenn der Code drin bleibt, dann arbeitet er auch, 

was für ein Optimier-Feature..

hilfreich wäre ein Log zumindest nach einem Test-Lauf welches anzeigt, welcher Code irgendwann mal (temporär) wegoptimiert wurde..

Interessant (aber etwas irritierend), dass ein erfahrener (!?!) (Java) Programmierer scheinbar nach langer Zeit (!?) zu bemerken scheint:

Multithreading ist schwierig

Ja. Nicht „oh, das ist aber ein kompliziertes Integral in dieser Formel, die ich da in Code gießen soll“, sondern: „Was heißt denn ‚Happens-Before‘ jetzt genau? Memory Consistency Errors (The Java™ Tutorials > Essential Java Classes > Concurrency) hilft mir da nicht…“.

Falls ich das hier

werden wirklich die kompletten Objekte in einen lokalen Speicher gelegt und bekommen dann womöglich andere Belegung der internen Attribute wie boolean?
wie sonst…

hilft volatile auf das eigene Attribut frame (im GamePanel)?,
oder achtet das nur darauf, ob evtl. ein anderes Objekt in das Attribut gelegt wird?

wenn man sich das JFrame in eine lokale Variable holt (etwa vor langer Game-Schleife), was sich ja allgemein als Cache etwas anbietet,
dann ist das Attribut mit volatile oder nicht eh ausgeschaltet…

Richtig verstanden habe: Das volatile bezieht sich bestenfalls auf eine Referenz, aber NICHT auf die Fields des Objektes, auf das die Referenz zeigt (außer wenn die auch wieder als „volatile“ markiert sind). Und es bezieht sich auch nicht auf das Objekt, sondern auf die Referenz selbst. Bei

volatile Integer foo = 32;
Integer bar = foo;

ist nur eins von beidem volatile, und das andere weiß davon nichts. Klassische Kopfschmerzen verursacht diese Tatsache, wenn man gerne einen „volatile array“ hätte. Einen Array als volatile zu deklarieren, macht seine Elemente eben nicht volatile. Für sowas gibt’s dann AtomicIntegerArray (Java Platform SE 8 ) & Co.

(Eine Volltextusuche auf der Kniffel-Seite brachte kein Ergebnis für „Thread“, das sich NICHT auf Foren-Threads bezog, deswegen weiß ich gerade nicht, worum es da ging).

Quaxlis Tutorial hatte den einen oder anderen Stolperstein, was Synchronisation angeht.

Ansonsten, z.B. auch konkret in bezug auf das „frame.isVisble()“-Beispiel: Da wird „meistens“ irgendwo synchronisiert. (Meistens auf den „tree lock“). Die Tragweite und Implikationen sind aber sonst nicht unerheblich. Immer, wenn irgendwas zwischen mehreren Threads geshart wird, muss man sich darüber Gedanken machen. Unerfreulicherwesie scheinen diejenigen, die sich darüber keine Gedanken machen, effizienter zu arbeiten und schneller kompakteren und einfacheren Code zu produzieren. Wer glaubt, Programmieren sei leicht, der möge mir bescheid sagen, und ich erkläre ihm dann, warum er Unrecht hat.

Bei Quaxlis Tutorial spielt das auch eher weniger eine Rolle. In komplexeren Programmen mit entsprechenden Eingabemethoden müssen die Eingaben sowieso erst einmal zwischengespeichert werden um Wettlaufsituationen und ConcurrentModificationExceptions zu vermeiden und sie kontrolliert und geordnet in den Programmablauf zu integrieren, z.B. mit einer ConcurrentLinkedQueue.

Generell sollte man Threads klar voneinander abgrenzen, jeder Thread muss einen abgekapselten Bereich an Variablen haben in dem er arbeiten kann. Andere Threadberreiche sind read-only.
Lässt sich in Java leider nur schlecht umsetzen wenn man anfängt das mit anderen Modellen zu mixen. Dafür würde man einen weiteren Modifier und ein erweitertes Packagemodell benötigen, denke ich.
Stattdessen ist das ganze meistens ein riesiges Chaos in dem Threads komplett durcheinander und unübersichtlich durch den Code wüten.
Deswegen vielen auch nicht klar, dass der ActionListener von einem anderen Thread aufgerufen wird, weil sie es nicht wissen und auch garnicht wissen können bzw. weil es schlicht möglich ist von der ActionListener-Methode überhaupt Daten zu verändern die von einem anderen Thread verwaltet werden.

Mich würde aber auch mal interessieren nach welchen Kriterien die JVM entscheidet den Code einfach zu entfernen? Wenn die Setter-Methode nicht von selbigem Thread aufgerufen wird der sie auch verwendet?

In Java muss man sich darüber auch meistens absolut keine Gedanken machen weil die Sprache und die Bibliotheken entsprechend Idiotensicher sind.
In C++ sieht die ganze schon wieder ganz anderst aus, wenn sich praktisch 90% des Codes um Memory Management dreht und bei Fehlern anfängt „undefined behaviour“ von Absturz bis Bluescreen zu entwickeln.