Kurioses Threadverhalten, wer weißt Rat?

CongoFX

Mitglied
Hallo Java Freunde,

ich bin neulich auf etwas kurioses gestoßen.

In einer Klasse welche von java.lang.Thread erbt, ist wie bekannt die run() Methode implementiert, welche einen Punkt auf einen Canvas gui malt.

Während jedem Durchlauf wird der Wert der deklarierten integert Variablen lineX um eins jeweils inkrementiert. Sofern der Wert von 600 überschritten ist, wird er auf 0 zurück gesetzt.

Der Thread wird gestartet wenn die Maus über dem Canvas gui ist und der Thread wird angehalten sofern die Maus den Canvas gui verläßt. (mouseEntered() & mouseExited())

Alles funktioniert wie geplant, es wird gemalt sofern die Maus in den Canvas eintritt und das Malen wird gestoppt wenn die Maus den Canvas verläßt, realisiert mit wait() und notify().


Erst einmal der Code:

Java:
public class MeineClasse extends Thread
{
	private volatile Canvas gui;
	private volatile MouseListener mouseListener;
	private volatile boolean _suspendFlag;

	public Canvas getCanvas()
	{
		return gui;
	}

	public MeineClasse()
	{
		_suspendFlag = false;

		mouseListener = new MouseListener(this);

		gui = new Canvas();
		gui.setBounds(0, 0, 640, 480);
		gui.addMouseListener(mouseListener);
		gu.addMouseMotionListener(mouseListener);
	}

	@Override
	public void run()
	{
		int lineX = 0;
		gui.createBufferStrategy(2);
		BufferStrategy strategy = gui.getBufferStrategy();
		Thread currentThread = Thread.currentThread();

		while (currentThread == this)
		{
			try
			{
				currentThread.sleep(33);

				if (_suspendFlag)
				{
		   			synchronized (this)
					{
						while (_suspendFlag)
						{
							wait();
						}
					}
				}
			}
			catch (InterruptedException e)
			{
			}

			// lineX wird trotz wait(); weiter berechnet
			lineX = lineX > 600 ? 0 : (lineX+ 1);

			g = strategy.getDrawGraphics();
			g.setColor(Color.WHITE);
			g.fillRect(0, 0, gui.getWidth(), gui.getHeight());

			g.setColor(Color.BLACK);
			g.drawLine(lineX, 0, lineX, 0);
			g.drawString(String.valueOf(lineX), 2, 20);

			strategy.getDrawGraphics().dispose();
			strategy.show();
		}
	}

	public class MouseListener extends MouseAdapter implements MouseMotionListener
	{
		private final DrawerBase parent;
		
		public MouseListener(DrawerBase argParent)
		{
			parent = argParent;
		}

		@Override
		public synchronized void mouseEntered(MouseEvent e)
		{
			e.consume();

			if (parent.isAlive())
			{
				_suspendFlag = false;

				synchronized (parent)
				{
					 parent.notify();
				}
			}
			else
			{
				parent.start();
			}
		}

		@Override
		public synchronized void mouseExited(MouseEvent e)
		{
			e.consume();

			_suspendFlag = true;
		}
	}
}


Das Problem

Nun das kuriose Problem, die Berechnung des Wertes von lineX wird jedoch "irgendwie" weiter fortgesetzt, obwohl der Thread zu diesem Zeitpunkt angehalten ist.


Ein kurze Hintergrundinformation

Warum wurde das so gelöst? Der Sinn ist folgender, sofern das Objekt erstellt und initialisiert wurde, kann der Canvas anhand von meineKlasse.getCanvas() einem Container Objekt wie einem Panel, Frame oder Applet hinzugefügt werden. Es malt sich mit seiner Logik doppelt gepuffert (double buffering).


Wer kennt dieses Problem und kann mir eine Hilfestellung geben? ???:L

Ich freue mich über jegliche Antworten und bedanke mich im Vorfeld für die freundliche Hilfe.
 

ice-breaker

Top Contributor
Der ganze Threading-Rest kommt mir aber auch sehr komisch vor. Es spllte dir auch eigentlich egal sein welcher Thread die run-Methode ausführt, zudem erweitert man nicht die Klasse Thread sondern implementiert das Runnable-Interface.
Vllt könntest du den Code mal mehr erläutern (Kommentare) und noch ein bisschen was dazu erzählen.

Wieso wird der Thread eigentlich mehrmals neugestartet? Lass ihn doch laufen, und wenn er nichts mehr zeichnen soll, lass ihn mit wait einschlafen, bis er wieder zeichnen soll.
 
Zuletzt bearbeitet:

CongoFX

Mitglied
Schonmal geschaut ob vielleicht die interruptedException fliegt?

Nein die Exception fliegt nicht.


Wieso wird der Thread eigentlich mehrmals neugestartet? Lass ihn doch laufen, und wenn er nichts mehr zeichnen soll, lass ihn mit wait einschlafen, bis er wieder zeichnen soll.

Das passiert doch, wie beschrieben mit wait() und notify().


Der ganze Threading-Rest kommt mir aber auch sehr komisch vor. Es spllte dir auch eigentlich egal sein welcher Thread die run-Methode ausführt, zudem erweitert man nicht die Klasse Thread sondern implementiert das Runnable-Interface.
Vllt könntest du den Code mal mehr erläutern (Kommentare) und noch ein bisschen was dazu erzählen.

Ich dachte das es kein Problem ist "Thread" als Basisklasse zu verwenden, wird auf der Oracle Seite sogar als Beispiel angegeben. Aber ich versuche mal die Alternative mit dem Runnable Interface und poste hier noch einmal mein Result.
 

Michael...

Top Contributor
Hier mein Vorschlag:
Verwende Swing statt AWT die Swingkomponenten verwenden standardmässig double buffering
Verwende ein Objekt SuspendFlag (s. Klasse SuspendFlag) statt eines einfachen booleans, dann kannst Du darüber auch wait() und notify() steuern

Java:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class ThreadCanvasDemo extends JFrame {
	
	class ThreadCanvas extends JPanel implements Runnable {
		private SuspendFlag flag;
		private int lineX;
		
		public ThreadCanvas() {
			this.flag = new SuspendFlag();
			this.flag.setSupsendingStatus(true);
			this.setBackground(Color.WHITE);
			this.lineX = 0;
			
			new Thread(this).start();
			
			this.addMouseListener(new MouseAdapter(){
				public void mouseEntered(MouseEvent e) {
					flag.setSupsendingStatus(false);
				}

				public void mouseExited(MouseEvent e) {
					flag.setSupsendingStatus(true);
				}
			});
		}
		
		public void run() {
			while(true) {
				try {
					synchronized(flag) {
						if(flag.isSuspending())
								flag.wait();
					}
					Thread.sleep(40);
					lineX = (lineX + 1)%600;
					System.out.println(lineX);
					repaint();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			if(!flag.isSuspending()) {
				g.fillRect(lineX, 0, 10, 10);
				g.drawString(String.valueOf(lineX), 2, 20);
			}
		}
		
		class SuspendFlag {
			private boolean suspendFlag = false;
			
			public synchronized void setSupsendingStatus(boolean b) {
				this.suspendFlag = b;
				this.notifyAll();
			}
			
			public boolean isSuspending() {
				return this.suspendFlag;
			}
		}
	}
	
	public ThreadCanvasDemo() {
		this.getContentPane().add(new ThreadCanvas());
	}
	
	public static void main(String[] args) {
		JFrame frame = new ThreadCanvasDemo();
		frame.setBounds(0, 0, 500, 300);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
}
 

CongoFX

Mitglied
Das Problem konnte ich nun mit folgender Maßnahme lösen, indem ich die interrupt() aufrufe (siehe Änderung 1), sofern das booleansche _suspendFlag true ist, also der Thread warten soll. Zudem habe ich den auszuführenden Code noch einmal in der Schleife an die Bedingung des _suspendFlag gebunden (siehe Änderung 2).


Der Code sieht nun wie folgt aus:

Java:
public class MeineClasse extends Thread
{
	private volatile Canvas gui;
	private volatile MouseListener mouseListener;
	private volatile boolean _suspendFlag;
 
	public Canvas getCanvas()
	{
		return gui;
	}
 
	public MeineClasse()
	{
		_suspendFlag = false;
 
		mouseListener = new MouseListener(this);
 
		gui = new Canvas();
		gui.setBounds(0, 0, 640, 480);
		gui.addMouseListener(mouseListener);
		gui.addMouseMotionListener(mouseListener);
	}
 
	@Override
	public void run()
	{
		int lineX = 0;
		gui.createBufferStrategy(2);
		BufferStrategy strategy = gui.getBufferStrategy();
		Thread currentThread = Thread.currentThread();
 
		while (currentThread == this)
		{
			try
			{
				currentThread.sleep(33);
 
				if (_suspendFlag)
				{
					//  Änderung 1: Interrupt thread - der Aufruf wird standardmäßig eine interrupted exception werfen 
					interrupt();

					synchronized (this)
					{
						while (_suspendFlag)
						{
							wait();
						}
					}
				}
			}
 			catch (InterruptedException e)
			{
			}
 
			//  Änderung 2: Den Code nur ausführen wenn das Flag true ist
			if (!_suspendFlag)
			{
				lineX = lineX > 600 ? 0 : (lineX+ 1);
 
				g = strategy.getDrawGraphics();
				g.setColor(Color.WHITE);
				g.fillRect(0, 0, gui.getWidth(), gui.getHeight());
 
				g.setColor(Color.BLACK);
				g.drawLine(lineX, 0, lineX, 0);
				g.drawString(String.valueOf(lineX), 2, 20);
 
				strategy.getDrawGraphics().dispose();
				strategy.show();
			}
		}
	 }

	public class MouseListener extends MouseAdapter implements MouseMotionListener
	{
		private final DrawerBase parent;
        
		public MouseListener(DrawerBase argParent)
		{
			parent = argParent;
		}
 
		@Override
		public synchronized void mouseEntered(MouseEvent e)
		{
			e.consume();
 
			if (parent.isAlive())
			{
				_suspendFlag = false;
 				synchronized (parent)
				{
					parent.notify();
				}
			}
			else
			{
				parent.start();
			}
		}
 
		@Override
		public synchronized void mouseExited(MouseEvent e)
		{
			e.consume();
			_suspendFlag = true;
		}
	}
}


Mit dieser Lösung funktioniert nun alles wunderbar.


Mein Fazit:

Ich denke mal eher dass das Problem durch mein Mißverständnis von der Java Threading Technik ausgelöst wurde. Unter C++ oder C# funktioniert Threading etwas anders, bzw. ist eine einfachere Umsetzung möglich (ohne das Thema Dead Locks oder Thread monitoring nun aufrollen zu wollen).
Unter Java "schläft" der Thread zwar mit wait(), jedoch scheint es das er alle verpassten Berechnungen beim Fortfahren des Threads nachträglich berechnet und dann dann normal fortfährt.

Ein Beispiel, ein Wert wird jede Sekunde um eins inkrementiert, nun setzt der Thread für fünf Sekunden aus und fährt wieder fort, dann sollte das normaler Weise so aussehen:

Code:
Thread aktiv	ja	ja	nein	nein	nein	nein	nein	ja	ja	ja
Sekunde		1	2	3	4	5	6	7	8	9	10
Wert		20	21	21	21	21	21	21	22	23	24

Das Problem welches nun ich hatte:

Code:
Thread aktiv	ja	ja	nein	nein	nein	nein	nein	ja	ja	ja
Sekunde		1	2	3	4	5	6	7	8	9	10
Wert		20	21	21	21	21	21	21	27	28	29

Bei Sekunde acht wurde Thread fortgesetzt. Nun das Kuriose, die Inkrementierung von Sekunde drei bis sieben wurde "schnell" nachgeholt und anschließend lief der Thread normal weiter.

Nun ja, mit interrupt() konnte das Problem gelöst werden, aber evtl. sitzen noch andere Leute in der Patsche und ihnen kann mit dem Post geholfen werden.


Hier mein Vorschlag:
Verwende Swing statt AWT die Swingkomponenten verwenden standardmässig double buffering
Verwende ein Objekt SuspendFlag (s. Klasse SuspendFlag) statt eines einfachen booleans, dann kannst Du darüber auch wait() und notify() steuern


Hallo Michael, vielen Dank für Deinen überaus konstruktiven Post, ich werde bei Gelegenheit Deinen Lösungsansatz einmal implementieren und ausprobieren. ... aber vielen Dank für die Antwort.

... und natürlich auch ein Dankeschön an alle anderen Poster! :)
 

Michael...

Top Contributor
Unter Java "schläft" der Thread zwar mit wait(), jedoch scheint es das er alle verpassten Berechnungen beim Fortfahren des Threads nachträglich berechnet und dann dann normal fortfährt.
Nein, wenn ein Thread sauber "schlafen" gelegt wird, dann holt er auch nichts nach.
Kannst ja mal compilierbaren Code posten, der sich so verhält.

So wie Du es momentan gelöst hast, glaube ich nicht, dass der Thread tatsächlich schläft. Du verhinderst einfach nur (durch die if Abfrage in Zeile 57) das weiter inkremenitert und etwas gezeichnet wird.
 

Neue Themen


Oben