Thread.sleep in while

Hallöle,

ich hätte da eine etwas obskurere Frage.
Ich habe einen Boolean und würde gerne solange dieser true ist jede Sekunde die Zahl in einem JLabel verändern. Von 0 bis 9.

Bisher habe ich das hier:

		boolean start = true;
		Random wuerfel = new Random();
		while(start) {
			Thread thread = new Thread(new Runnable() {
				  @Override
				  public void run() {
					  try {
						  Thread.sleep(1000);
						  int x = wuerfel.nextInt(10);
						  eins.setText(x+""); //das ist das JLabel
						  } catch(InterruptedException e) {}
					  }
				  }
			);
			thread.start();
		}
	}```

Ich bin mir nun einer Sache bewusst. Wie ich dann auch irgendwann festgestellt habe, macht die while Schleife natürlich die ganze Zeit ihre Durchläufige, ohne dabei auf den Inhalt von Sleep zu warten. Also überlagern sich sozusagen viele While-Durchgänge und ich kann das mit sleep(1000) vergessen, so wie ich das sehe.

Kann mir jemand verraten, wie ich das verhindern kann und mein oben genanntes Ziel erreiche?
/* Im Übrigen fällt mir gerade auf, dass ich den thread nicht adäquat korrekt zumache, da ja nach dem Start nichts mehr kommt. Das muss ich mir auch nochmal ansehen. */

Vielen Dank und schöne Grüße
Lukas

EINEN Thread starten, in der run-Methode eine Schleife,
wie sie abgebrochen werden soll ist so oder so noch offen, wer setzt start evtl. auf false? müsste Instanzattribut sein

Du solltest die Schleife innerhalb des Threads laufen lassen. So wie es jetzt ist, wird in jedem Durchgang ein neuer Thread gestartet, was wohl nicht dein Ziel ist.
So in etwa:

                  @Override
                  public void run() {
                      while(start) {
                            try {
                                  Thread.sleep(1000);
                                  int x = wuerfel.nextInt(10);
                                  eins.setText(x+""); //das ist das JLabel
                            } catch(InterruptedException e) {}
                      }
                 }
                      
     });
    thread.start();```

Hallöle und Danke euch beiden für eure Antworten,

@Bene
Nun ja, dass man das tauschen könnte, das ist wieder so eine Sache. So nahe, aber ich komme da nicht drauf. :smiley: So hat das erstmal funktioniert, danke schön.

@SlaterB
Ähm, meine Intention ist folgende. Ich wollte mich ein wenig mit Threads und Zufall beschäftigen und habe mir einen billigen primitiven Einarmigen Banditen geschaffen.
Ich lade mal ein Bild dazu hoch.

Durch klick auf eines der Drei JLabel fängt er an durchzurauschen und bei weiterem klick schaltet der Boolean um und stoppt.

Das klappt nun auch so! :slight_smile: Entschuldige, dass ich das mit dem boolean verheimlicht hatte, ich dachte das wäre irrelevant. Man soll das Problem ja immer begrenzen. :wink:

Die Methode sieht nun so aus:

		start = true;
		Random wuerfel = new Random();
		Thread thread = new Thread(new Runnable() {
			  @Override
			  public void run() {
				  while(start) {
					  try {
						  Thread.sleep(50);
						  int x = wuerfel.nextInt(10);
						  int y = wuerfel.nextInt(10);
						  int z = wuerfel.nextInt(10);
						  eins.setText(x+"");
						  zwei.setText(y+"");
						  drei.setText(z+"");
						  } catch(InterruptedException e) {}
					  }
				  }
			  }
		);
		thread.start();
	}```

Hat jemand noch einen Verbesserungsvorschlag bzw. eine Anmerkung, dass ich etwas ungünstig programmiert hätte?

Vielen Dank!
Gruß
Lukas :)

Und dabei keinesfalls vergessen, die start-Variable volatile zu machen!

Achtung : Swing-GUI darf nur durch den EDT verändert weden !
Wenn also schon dann wäre wenn überhaupt sowas in die Richtung :

{
	boolean start = true;
	Random wuerfel = new Random();
	Thread thread = new Thread(new Runnable()
	{
		@Override
		public void run()
		{
			while(start)
			{
				try
				{
					Thread.sleep(50);
					int x = wuerfel.nextInt(10);
					int y = wuerfel.nextInt(10);
					int z = wuerfel.nextInt(10);
					SwingUtilities.invokeAndWait(new Runnable()
					{
						@Override
						public void run()
						{
							eins.setText(String.valueOf(x));
							zwei.setText(String.valueOf(y));
							drei.setText(String.valueOf(z));
						}
					});
				}
				catch(InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
	});
	thread.start();
}```
ABER : Geht auch nicht weil der bool innerhalb einer anonymen Klasse genutzt wird und daher FINAL sein muss, sonst gibts Compiler-Error !
Gleiches gilt für Random.
Also muss man sich einen Wraper drum bauen was dann in sowas hier ausartet:
```final SuperClass.Flag flag=new SuperClass.Flag(true)
private void zufall()
{
	final Random wuerfel=new Random();
	Thread thread=new Thread(new Runnable()
	{
		@Override
		public void run()
		{
			while(flag.isFlag())
			{
				try
				{
					Thread.sleep(50);
					int x = wuerfel.nextInt(10);
					int y = wuerfel.nextInt(10);
					int z = wuerfel.nextInt(10);
					SwingUtilities.invokeAndWait(new Runnable()
					{
						@Override
						public void run()
						{
							eins.setText(String.valueOf(x));
							zwei.setText(String.valueOf(y));
							drei.setText(String.valueOf(z));
						}
					});
				}
				catch(InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
	});
	thread.start();
}

//...

static class Flag
{
	private boolean flag=false;
	public Flag(boolean flag)
	{
		this.flag=flag;
	}
	public void toggle()
	{
		flag=!flag;
	}
	public boolean isFlag()
	{
		return flag;
	}
}```
Außerdem muss das Flag außerhalb der Methode definiert werden, denn sonst kommst du nie wieder an dieses ran um es zu togglen. Es würde also in deinem original Code IMMER true bleiben da du ja "von außen" nirgends eine Referenz drauf hast um es false zu setzen.

Zusätzlich : Gewöhne dir sowas wie `int+""` zum "cast" eines int in einen String bitte gar nicht erst an. Der Compiler baut daraus nämlich sowas wie `(new StringBuilder()).append(int).append("").toString()`. Nutze lieber sowas wie `String.valueOf(int)`.
Auch noch : NIE never ever leer catch-Blöcke. Wenn überhaupt gehört mindestens ein prinStackTrace() rein oder zumindest ein Logger. Auch wenns in dem Fall nicht weiter stören würde kann man so zumindest nachvollziehen warum das sleep() unterbrochen wurde.

Kapier’ ich gerade nicht…

Das invokeAndWait sollte auch nicht nötig sein. (Bestenfalls theoretisch, wenn man davon ausgeht, das das Setzen des Textes sehr lange dauert…)

Und „Ausarten“… ja, man kann das etwas abschwächen, durch ein paar Methoden…mit sowas wie

    private volatile boolean start = true;
    
    private void zufall()
    {
        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                runRandomNumbers();
            }
        });
        thread.start();
    }
    
    private void runRandomNumbers()
    {
        Random wuerfel = new Random();
        while(start)
        {
            int x = wuerfel.nextInt(10);
            int y = wuerfel.nextInt(10);
            int z = wuerfel.nextInt(10);
            setRandomNumbers(x, y, z);
            try
            {
                Thread.sleep(50);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    private void setRandomNumbers(int x, int y, int z)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                eins.setText(String.valueOf(x));
                zwei.setText(String.valueOf(y));
                drei.setText(String.valueOf(z));
            }
        });
    }

geht das finde ich sogar…

Hmm, hab das grad noch mal selbst getestet und scheinbar ist ein final bei anonymen Klassen nicht (mehr) nötig … bin der Meinung das war mal anders und führte zu nem Compile-Error … na egal.

Zum invokeAndWait() : hatte ich bewusst gewählt da so sichergestellt wird das die Änderung der GUI komplett ist bevor weitergewürfelt wird. Mit invokeLater() kann es nämlich passieren dass das Update der GUI länger dauert als das Timeout des Würfels. Ist zwar nicht schlimm da die Queue nach Reihenfolge abgearbeitet wird, aber es kann zu ungleichmäßiger Animation kommen.

*** Edit ***

Schade - EDIT geht nicht mehr …

Zum final : Ich hab noch mal Google gefragt und es ist immer noch : Variablen von äußeren Klassen müssen final sein um auf diese in inneren zugreifen zu können. Nun wurde in Java 8 der Compiler lediglich soweit verbessert das er sog. “effective final”, also Variablen die zwar nich final sind, aber bei denen feststellbar ist das sich ihr Wert zur Runtime nicht ändern wird, zu erkennen. Kommt also ei wenig auf scope an und wie man den Code strukturiert.

Ja, “effectively final” is neu (ich schreib’ trotzdem oft noch das “final” hin… :rolleyes: )

Dass ohne das “…AndWait” theoretisch die Queue überfüllt werden könnte, meinte ich mit
Bestenfalls theoretisch, wenn man davon ausgeht, das das Setzen des Textes sehr lange dauert…
Da könnte man sich bestimmt auch was trickreiches überlegen, aber das dürfte in dem Fall nicht so wichtig sein.

Hallöle,

ich möchte hierdran noch einmal mit einer Frage anknüpfen.
Ich arbeite in meinem aktuellen Projekt viel mit Threads und möchte mal so generell wissen, ob die Form, wie ich sie aktuell anwende überhaupt hinhaut und logisch ist.

		Thread thread = new Thread(new Runnable() {
			  @Override
			  public void run() {
				  try {
					  Thread.sleep(1500);
					  Barkartenecke.getBarzelle(5).setBorder(BorderFactory.createLineBorder(Color.black));
					  } catch(InterruptedException e) {}
				  }
			  }
		);
		thread.start();```

`Barkartenecke.getBarzelle(5).` ist in dem Fall ein JPanel, wo ein Rand drum herum gesetzt wird. Wie zu sehen wird der Rand Rot und soll nach 1,5 Sekunden wieder Schwarz werden.


Klappt so weit ganz ganz gut ohne Probleme, aber ich frage mich, ob das so wie es ist richtig programmiert ist.
Habe ich irgendetwas nicht beachtet? Kann man das besser lösen?
Und vor allem:
Ich habe am Ende `thread.start();`. Geht das überhaupt von allein zu oder muss ich den Thread danach noch irgendwie schließen können?

Es wäre schön, wenn mir da einer helfen könnte.

Dankeschön!
Gruß
Lukas :)

Auch da gilt, dass das setBorder wieder auf den Event-Dispatch-Thread gemacht werden müßte - analog zu dem “setText” bei dem Label.

Ansonsten läuft der Thread ja höchstens 1.5 Sekunden. Wenn er länger laufen könnte (speziell, wenn er “unendlich lange” läuft), könnte man ihm VOR dem starten noch ein
thread.setDaemon(true);
spendieren: Damit wird er (grob gesagt) beendet, wenn das Programm beendet wird (ansonsten würde dieser Thread dafür sorgen, dass das Programm noch (bis zu 1.5 Sekunden) “am Leben” bleibt).

Also schön finden tue ich den Code nicht, aber er macht ja das was du willst.
Die frage die sich mir dabei stellt ist, warum willst du Zeitversetzt den Rahmen die Farbe ändern?
Du hast geschrieben du hast viele Threads, daraus nehme ich an, du machst das bei anderen Elementen deiner UI auch
Wenn ja, haben die alle den selben Abstand/Zeitintervall? Dann kann man das ja alles in einen Thread machen.

Ansonsten wurde ja schon vorher in den Posts schon einiges zum Thema Threads und Schleifen geschrieben, was nicht unbedingt alles wiederholt werden muss.

Hallöle und Danke für Eure Antworten,

es handelt sich hierbei um ein Kartenspiel, wo man Karten auch aneinander legt. Und die Rahmen sind Standardmäßig Schwarz. Wenn ein Spieler einen ungültigen Zug macht, dann blinken die daran beteiligten Karten 1.5 Sekunden auf, um dem Spieler noch einmal kurz zu visualieren, wo der Fehler in seinem Spielzug liegt. Daher die Rahmenfarbenänderung nach Zeit.

Wenn ja, haben die alle den selben Abstand/Zeitintervall? Dann kann man das ja alles in einen Thread machen.

Leider sind das eine Menge an verschiedenen Kartenarten, die mal 1.5,3 oder 5 Sekunden lang Schwarz, Rot oder Grün aufblinken. Klar kann ich das jetzt mit nem int farbe und int zeit und dann so ner Verschachteln if-Bedingung in die Schleife reinhauen. Aber nun gut. War bisher nicht meine Intention. :smiley:

Ich möchte den Thread als solchen, wie er hier gepostet wurde einmal komplett einzeln betrachten:

        Thread thread = new Thread(new Runnable() {
              @Override
              public void run() {
                  try {
                      Thread.sleep(1500);
                      Barkartenecke.getBarzelle(5).setBorder(BorderFactory.createLineBorder(Color.black));
                      } catch(InterruptedException e) {}
                  }
              }
        );
        thread.start();```


 @Marco13 
Zu dem `thread.setDaemon(true);`. Heißt das so viel wie, dass der Thread in den Hintergrund gerückt wird und "nebensächlich" ausgeführt wird?

Soll der dann in die Zeile hinter ` Thread thread = new Thread(new Runnable() {` ?
Weil Du sagtest davor und das ist das "weiteste davor", was es gibt, wo der Thread schon erstellt wurde. :D

Und ähm noch eine Frage zu dem Beenden generell:
da steht ja jetzt nur thread.start(). Beendet der sich von allein, wenn das was drin steht ausgeführt wurde, oder ist da auch sowas wie .close() nötig?


Vielen Dank für die Beantwortung meiner Fragen.

Gruß
Lukas

Nein. Das bezieht sich nur auf die Beendigung. Solange noch ein Thread läuft, der kein Daemonthread ist, kann das Programm nicht beenden. Das ist vor allem problematisch, wenn die Threads unendlich lange laufen (wie z. B. bei einem ExecutorService), weil das Programm niemals beendet.
Zitat aus den JavaDocs:

The Java Virtual Machine exits when the only threads running are all daemon threads.

*** Edit ***

[quote=FranzFerdinand;116971]Und ähm noch eine Frage zu dem Beenden generell:
da steht ja jetzt nur thread.start(). Beendet der sich von allein, wenn das was drin steht ausgeführt wurde, oder ist da auch sowas wie .close() nötig?[/quote]
Ja, der Thread beendet sich von alleine, wenn die run-Methode durch ist. Es ist also kein .close() o. ä. nötig.

Wie @Marco13 schon angemerkt hat ist halt wichtig das Änderungen an der (Swing-)GUI halt nur durch den EDT selbst laufen. Daher ist der Code an sich so “verkehrt” das du von einem externen Thread direkt die GUI manipulierst, was man bei Swing halt vermeiden soll. Du musst also das Runnable anstatt einem neuen eigenen Thread dem EDT übergeben. Das geht, wie oben bereits geschrieben, z.B. durch SwingUtilities.invokeLater() bzw .invokeAndWait().

„Nebensächlich“? Wie auch immer, das hat nichts mit einer Priorität oder so zu tun. Es bedeutet nur, dass er sofort beendet wird, wenn das Programm beendet wird (und, wie schon gesagt wurde: Wenn er selbst fertig ist, solange das Programm noch läuft, ist nichts weiter zu tun). Das „setDaemon(true)“ muss irgendwo zwischen dem
Thread thread = new Thread(…);
und dem
thread.start();
aufgerufen werden.

Hallöle ihr Alle,

vielen Dank! Das mit dem setDaemon hab ich jetzt verstanden. Man lernt immer was dazu. Tolles Forum! :slight_smile: Ist wohl auch der Grund, warum ich in 1-2 Programmen, in denen ich Threads nutze immer mal ein paar Sekunden, wenn ich ein Programm neu starten möchte die Meldung kriege, das Programm wäre doch noch am Laufen. Ist wohl noch ein Thread offen.
Es sieht jetzt so aus:

		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1500);
					Barkartenecke.getBarzelle(5).setBorder(BorderFactory.createLineBorder(Color.black));
					} catch(InterruptedException e) {}
				}
			}
		);
		thread.setDaemon(true);
		thread.start();```

Was das InvokeLater angeht, bin ich mir noch nicht so ganz sicher. Ich habe viel gegooglet und bin viel verwirrt worden.
Es sieht bei mir jetzt so aus:
```Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						try {
							Thread.sleep(1500);
							Barkartenecke.getBarzelle(6).setBorder(BorderFactory.createLineBorder(Color.black));
						} catch(InterruptedException e) {}
					}
				});
			}
		});
		thread.setDaemon(true);
		thread.start();```

Bzw. das ist die einzige Variante, die mir Eclipse hat durchgehen lassen. Ist das so richtig? Weil das mit den zwei run-Methoden ist doch irgendwie unnütz geschachtelt, oder?

Vielen Dank!
Gruß
Lukas :)

Jein. Die Schachtelung ist schon richtig so.

Aber

das Thread.sleep steht an der falschen Stelle! Dort legst du ja den Event-Dispatch-Thread schlafen, und das ist ja genau das, was du NICHT willst. Stattdessen soll der “Äußere” Thread schlafen.

Wie schon weiter oben steht: Das kann schnell unübersichtlich werden. Ich würde in so einem Fall Utility-Methoden einführen, GROB im Sinne von

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    waitAndChangeBorder();
                }
            });
            thread.setDaemon(true);
            thread.start();


...

private void waitAndChangeBorder()
{
    try {
        Thread.sleep(1500);
    } catch(InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    changeBorder();
}

private void changeBorder()
{
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            Barkartenecke.getBarzelle(6).setBorder(BorderFactory.createLineBorder(Color.black));
        }
    });
}

Alles klar. Habe ich verstanden. Funktioniert nun perfekt, so wie es ist. Und ja, es ist logisch, dass das Sleep außerhalb der ganz inneren Schleife sein muss. :slight_smile:
Was die Methodenschachtelung angeht, muss ich aus meiner Sicht ein wenig widersprechen. Ich finde es ohne Auslagerung in viele Methoden übersichtlicher. Ist aber auch subjektiv. Kann auch daran liegen, dass ich viele Threads habe und das dann richtig unübersichtlich wird.

Vielen Lieben Dank.
Ich danke dem ganzen Forum für seine große Kompetenz und immer wiederkehrende Freundlichkeit beim Helfen von Problemen! :slight_smile:

Gruß
Lukas

Hallöle,

eine kurze Ergänzung hab ich noch. Da funktioniert etwas irgendwie nicht, wie ich es möchte.

Ich habe folgenden Code:

					if(stuhl.getGast()!=null) {
						stuhl.getSpielzelle().setBorder(BorderFactory.createLineBorder(Color.red, 3));
						Thread thread = new Thread(new Runnable() {
							@Override
							public void run() {
								try {
									Thread.sleep(5000);
								} catch(InterruptedException e) {}
								SwingUtilities.invokeLater(new Runnable() {
									@Override
									public void run() {
										stuhl.getSpielzelle().setBorder(BorderFactory.createLineBorder(Color.black, 3));
									}
								});
							}
						});
						thread.setDaemon(true);
						thread.start();
					}
				}```

Stuhl und Tisch sind einfach zwei Klassen und getSpielzelle() nimmt sich die dazugehörigen JPanelElemente dazu heraus.

Mein Problem ist nun: Wie man sieht wird der Rand des Stuhls am Anfang Rot gefärbt und nach drei Sekunden Schwarz.

Er wird aber nie Rot. Aber der Code nach 5 Sekunden Schwarz funktioniert perfekt. Alles, was in dem Thread stattfindet, wird korrekt ausgeführt.
Nur ganz oben die Zeile mit dem Rot arbeitet nicht.
Ich habe sie auch mal versucht mit `System.out.println();` einzukesseln.
Wenn da steht:
```System.out.println("Hallo1");
stuhl.getSpielzelle().setBorder(BorderFactory.createLineBorder(Color.red, 3));
System.out.println("Hallo2");```

Dann gibt die Konsole aus:

Hallo1
Hallo2



Es wird also haargenau die Zeile davor und dahinter aufgerufen, nur die Zeile selbst nicht. Und von der Syntax her entspricht die Zeile ja außer der Farbe selbst haargenau der Zeile da weiter unten, die alles Schwarz macht und korrekt aufgerufen wird.

Ich habe es jetzt schon versucht mit einer anderen Farbe und mit Entfernen des Finals in Zeile 1 (Eclipse in Java 7 zwang mich mal dazu, aber in Java8 brauche ich das wohl nicht). Beide male konnte ich keine Problemlösung feststellen.

Hat jemand eine Idee, was ich da machen könnte?
Sieht jemand irgendwie einen logischen Sinn des Fehlers?

PS:
In einer anderen Methode steht das gleiche nochmal mit Tisch statt Stuhl und da geht es auch:
```for(final Tisch tisch:this.tische) {
				tisch.getSpielzelle().setBorder(BorderFactory.createLineBorder(Color.red, 3));
				Thread thread = new Thread(new Runnable() {
					@Override
					public void run() {
						try {
							Thread.sleep(5000);
						} catch(InterruptedException e) {}
						SwingUtilities.invokeLater(new Runnable() {
							@Override
							public void run() {
								tisch.getSpielzelle().setBorder(BorderFactory.createLineBorder(Color.black, 3));
							}
						});
					}
				});
				thread.setDaemon(true);
				thread.start();
			}```

Schöne Grüße
Lukas

Mehr Code hab ich leider nicht, das ist der einzige Code. Deshalb finde ich das ja auch so unlogisch. Da spielt nichts anderes eine Rolle. :)