Swing Thread mit wait() und notify() steuern

Burny91

Mitglied
Hallo Leute,

ich bin gerade dabei einen Zeit- und Punktecounter zu programmieren. Könnt ihr euch am beispiel von Solitare anschauen - unten rechts in der Statusbar.

Zum Problem: Ich habe einen Thread der periodisch einen Couter hochzählt (Sekunden) und einen Wert der jede Sekunde um einen Bestimmten Wert reduziert wird. Nun will ich diesen Thread mit einem Stop- und Resume-JButton steuern. Nun hält nicht mein Thread an, sondern meine GUI wird gestoppt =/

Wie muss ich meine ActionListener bestücken damit das oben beschriebene werkelt?

Java:
package timer_highscore2;

public enum Level {
    EASY, MEDIUM, DIFFICULT, INFERNAL;
}


package timer_highscore2;


import java.util.TimerTask;
import javax.swing.JLabel;

public class Score extends TimerTask {

	private int points = 0;
	private int reducer = 0;
	private int time = -1;
	public JLabel tmplabel;

	Score() {

	}

	Score(Level level, JLabel label) {
		
		tmplabel = new JLabel();
		tmplabel = label;
		
		if (level == Level.EASY) {
			points = 5005;
			reducer = 5;
		} else {
			if (level == Level.MEDIUM) {
				points = 10010;
				reducer = 10;
			} else {
				if (level == Level.DIFFICULT) {
					points = 15015;
					reducer = 15;
				} else {
					points = 20020;
					reducer = 20;
				}
			}
		}
	}
	
	public void stop(){
		try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void resume(){
		notify();
	}

	public int getPoints() {
		return points;
	}

	public int getTime() {
		return time;
	}

	public void run() {
		// Reducing point
		points -= reducer;
		// Time counting up each second
		time += 1;
		System.out.println("Score: " + points + " Time: " + time);
		tmplabel.setText("Score: " + String.valueOf(points) + " Time: " + String.valueOf(time));
	}
}


package timer_highscore2;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Timer;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class TestFrame_2 extends JFrame{

	public JLabel label;
	public Score score;
	public JButton stop;
	public JButton resume;

	TestFrame_2(){
		final Timer timer = new Timer();
		label = new JLabel("");
		score = new Score(Level.EASY, label);
		stop = new JButton("Stop");
		resume = new JButton("Resume");
		
		
		
		timer.scheduleAtFixedRate(score, 1000, 1000);

		setLayout(new FlowLayout());
		add(label);
		add(stop);
		add(resume);
		
		// Adding actionlisteners to buttons
		stop.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized(score){
					try {
						score.wait();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}				
			}
		});
		
		resume.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				score.resume();
			}
		});

		pack();
		setTitle("TestFrame_2");
		setSize(400, 200);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
	}

	public void update(){
		score.run();
	}

	public static void main(String args[]){
		TestFrame_2 tf2 = new TestFrame_2();
		tf2.update();

	}
}


Die Knackpunkte sind meiner Meinung nach die ActionListener
Java:
// Adding actionlisteners to buttons
		stop.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized(score){
					try {
						score.wait();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}				
			}
		});
		
		resume.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				score.resume();
			}
		});

Hoffe auf baldige Hilfe

Mfg Burny
 
Kommt da ein Fehler, oder was geht schief?

Wenn Score dein Thread ist, dann kannst du ihn mit wait() "suspenden" und mit notify() wieder loslaufenlassen.
 
Das was schief geht ist das die mein GUI einfriert. Ich möchte ja nur das mein Counter stoppt und nicht das Frame. Ich kann das Frame danach nur noch Killen um es zu schließen. Mein Button resume mit notitify funktioniert danach auch nicht mehr.
 
Ja, wenn in der ActionPerformed-Methode "score.wait();" aufgerufen wird, ist der Event-Dispatch-Thread (der sich um's GUI kümmert) blockiert. Die wait- und notify-Aufrufe sollten nur in dem Score-Objekt gemacht werden, und sich auf den Thread beziehen, der das ausführt. Wie man das machen würde, bzw. wie man an diese Thread ranokmmen sollte, weiß ich ehrlich gesagt gerade nicht ... ich würde eher einen ganz normalen Thread und keinen Timer verwenden.
 
Ah, Okay. Ja dann versuch ich das mal mit einem Thread. Hatte das nur mit dem Timer gedacht, weil mir der Thread zu overpowered vor kam.
 
Hab jetzt versucht mein Problem mit einem Thread zu lösen, bin aber wieder an der java.lang.IllegalMonitorStateException hängengeblieben -.-' Wie kann ich das umgehen? Ich möchte meinen Thread ja nur solange pausieren bis der User Einstellungen erledigt hat. Danach soll mein Timer-Thread weiterlaufen.

Mit suspend() und notify() kommm ich nicht weiter da diese beiden Methoden von JAVA als deprecated makiert sind. Mir ist außerdem noch synchronized( Object) über den weg gelaufen, aber dadurch wurde ich auch nicht schlauer.

Hier nochmal mein Code:

Java:
package ThreadDemo;

import javax.swing.JLabel;

public class Score extends Thread {

	private int points = 0;
	private int reducer = 0;
	private int time = -1;
	public JLabel tmplabel;
	private boolean cancel = true;

	Score(Level level) {

		tmplabel = new JLabel();
		// tmplabel = label;

		if (level == Level.EASY) {
			points = 25;
			reducer = 5;
		} else {
			if (level == Level.MEDIUM) {
				points = 10010;
				reducer = 10;
			} else {
				if (level == Level.DIFFICULT) {
					points = 15015;
					reducer = 15;
				} else {
					points = 20020;
					reducer = 20;
				}
			}
		}
	}

	public void run() {
		// Reducing point
		while (cancel) {
			if (points != 0) {
				points -= reducer;
			}

			try {
				/*
				 * KnackPunkt wie umgehe ich die
				 * java.lang.IllegalMonitorStateException??
				 */
				this.wait();
				System.out.println(this.isInterrupted());
				sleep(1000);
				this.notify();

			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			time += 1;
			System.out.println("Score: " + points + " Time: " + time);
		}
	}

	// Time counting up each second
	// tmplabel.setText("Score: " + String.valueOf(points) + " Time: " +
	// String.valueOf(time));

	public static void main(String args[]) {
		Score sc = new Score(Level.EASY);
		sc.start();

	}
}

Need help guys 🙁
 
Java:
				/*
				 * KnackPunkt wie umgehe ich die
				 * java.lang.IllegalMonitorStateException??
				 */
				this.wait();

->

Java:
			@Override
			public void actionPerformed(ActionEvent e) {
				synchronized(score){
					try {
						score.wait();
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}				
			}
		});

fällt dir ein Unterschied auf?

edit:
> Mir ist außerdem noch synchronized( Object) über den weg gelaufen, aber dadurch wurde ich auch nicht schlauer.

na gut, dazu nachlesen oder nicht, jedenfalls muss
synchronized(this) {
}
drumherum
 
Sauber :toll: hat funktioniert.

Nun habe ich noch eine Frage:
wenn ich den Thread mit wait() pausieren lasse, kann ich ihn mittels sleep() wieder aufwecken? Hab das zwar im Code stehen. Aber der Thread wurde ja angehalten 😉 also führt er die folgenden Zeilen nicht aus solange bis notify() aufgerufen worden ist - richtig?


Java:
			try {
				System.out.println(this.getState());

				synchronized (this) {
					this.wait();
					System.out.println(this.getState());
				}
				sleep(1000);
				this.notify();
				System.out.println(this.getState());

			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
 
Hey Leute ich steh jetzt vor einem neuem Problem.
Ich will nun in meiner GUI den Thread-Score mit einem Button starten. Nur kann ich nicht mit einer Instanz von Score auf die start() methode zugreifen um den Thread von außen zu starten. Wie macht man das? :bahnhof:

Java:
		sc = new Score(Level.DIFFICULT);
		//Set ActionListner
		start.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				sc.start();
			}
		});

Start() wird in meiner GUI als undefined markiert -.-'
 
wie genau lautet die Fehlermeldung?

was ist sc für eine Variable? wenn lokal in der umgebenen Methode definiert, dann sollte sie final sein:
final Score sc = new Score(Level.EASY);
 
Java:
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	The method start() is undefined for the type Score

	at timer_highscore.TestFrame.<init>(TestFrame.java:80)
	at timer_highscore.TestFrame.main(TestFrame.java:96)

Das ist die Fehlermeldung. Will die variable aber global haben, weil ich mit anderen Button den Thread, pausieren sc.wait(), resumen sc.notify() und abbrechen sc.cancel() will.

Falls es hilft meine GUI erbt nicht von Thread und hat auch nciht das Interface Runnable - könnte es vllt daran liegen?
 
In Java gibt es keine Clouser ;D

Daher musst du das sc als Objektvariable definieren, damit du in einer Subclass drauf zugreifen kasnnt.

Gruß,

Martin
 
Oh man hab meinen Fehler gefunden. Lag daran das ich mit zwei Scoreklassen gearbeitet habe und die eine im andern Paket lag -.-' *no comment*.

Jetzt habe ich meine Buttons der GUI so belegt das ich mit start() den Thread startet - das funktioniert. Nur mit dem wait() haut das nicht hin. Hatte das Problem oben schonmal geschildert, das wenn ich wait() aufrufe meine GUI anhält und der Thread einfach weiterläuft?! =( Ich will ja nur das mein Timer solange anhält bis ich ihn mit meinem notify-button dazu zwinge.

Java:
final Score sc = new Score(ThreadDemo.Level.DIFFICULT);

wait.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO Auto-generated method stub
				try {
					synchronized (sc) {
						sc.wait();
					}
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		});
 
Das liegt daran, dass du bei weitem noch nicht genug von Threads, wait und notify verstehst.

Im ActionListener befindest du dich im Event Dispatch Thread (EDT). Mit sc.wait() lässt du nun den EDT warten bis irgendwann ein notify kommt. Bei sc.wait() wartet nicht sc sondern der Aufrufer von sc.wait()!
 
Naja, irgendwo in deinem sc wirds ja wohl eine Endlosschleife der Art
Java:
while(true) {
    count--;
    Thread.sleep(1000);
}
geben.
Ich würde das jetzt erweitern und eine boolean-Variable
Java:
while(true) {
    if(running) {
        count--;
    }
    Thread.sleep(1000);
}
einbauen.
Die Variable muss natürlich auch gesetzt werden können
Java:
private volatile boolean running = true;
public void suspend() {
    running = false;
}
public void proceed() {
    running = true;
}
(Ich denke volatile müsste hier reichen, falls nicht muss man zu AtomicBoolean oder synchronized greifen um Thread-Sicherheit zu gewährleisten)

In deinem ActionListenern rufst du dann einfach sc.suspend()/sc.proceed() auf.

Müsste doch eigentlich gehen, oder? (bin schon müde)
 
wait() ist keine spezifische Thread Methode. Jedes Objekt besitzt die Methode wait(). Der aufrufende Thread wartet dann solange bis notify() bzw. notifyAll() an diesem Objekt aufgerufen wird.
D.h. wenn der Thread sc "warten" soll musst Du innerhalb dieses Threads wait() auf ein beliebiges Objekt aufrufen.
 
hey andiv,

das mit
Java:
public void suspend() {
    running = false;
}

hat funktioniert. Nur das mit proceed nicht 🙁 Wenn ich running = false setzte. bricht ja die while-schleife ab und folglich ist auch der Threadzuende, weil das alles in run() steht.

Wie kann ich das denn pausiern Leute? ;(

(Bin jetzt pennen - bis morgen)
 
Schau dir mal meine while-Schleife an, da hört nichts auf wenn running false ist, da wird halt nur der counter nicht runtergezählt.
 
Wenn ich das ganze richtig verstanden habe, willst Du per wait() einen Thread pausieren und mittels notify() weiter laufen lassen.

Hier mal ein Bsp. das ich vor einger Zeit mal für jemanden gemacht habe. Hierbei wird eine "Animation" per Thread gesteuert. Mittels wait() bzw. notifyAll() auf ein gemeinsames Objekt (in dem Fall das AnimationPanel) wird der Thread pausiert bzw. fortgesetzt:
Java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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

public class AnimationWait extends JFrame {
	public static void main(String[] args) {
		new AnimationWait();
	}

	private JButton start, stop;
	private AnimationPanel aniPanel;

	public AnimationWait() {
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setBounds(0, 0, 200, 200);
		this.setLocationRelativeTo(null);
		this.getContentPane().add(aniPanel = new AnimationPanel(),
				BorderLayout.CENTER);
		JPanel panel = new JPanel(new GridLayout(1, 2));
		panel.add(start = new JButton("Start"));
		panel.add(stop = new JButton("Pause"));
		stop.setEnabled(false);
		this.getContentPane().add(panel, BorderLayout.SOUTH);
		this.setVisible(true);
		
		new Thread(new AnimationThread(aniPanel)).start();
		
		start.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				aniPanel.setActive(true);
				start.setText("Resume");
			}
		});
		
		stop.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				aniPanel.setActive(false);
			}
		});
	}

	class AnimationPanel extends JPanel {
		private Color[] color = new Color[] { Color.RED, Color.BLUE, Color.ORANGE, Color.GREEN, Color.BLACK };
		private int colorIndex = 0;
		private boolean active = false;

		public void increaseIndex() {
			colorIndex = (colorIndex+1)%color.length;
			this.repaint();
		}
		
		public synchronized void setActive(boolean b) {
			this.active = b;
			start.setEnabled(!b);
			stop.setEnabled(b);
			this.notifyAll();
		}
		
		public synchronized boolean isActive() {
			return active;
		}

		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			g.setColor(color[colorIndex]);
			int w = this.getWidth(), h = this.getHeight();
			g.fillRect((w - 50) / 2, (h - 50) / 2, 50, 50);
		}
	}

	class AnimationThread implements Runnable {
		private AnimationPanel panel;
		public AnimationThread(AnimationPanel panel) {
			this.panel = panel;
		}
		
		public void run() {
			int i = 0;
			while (true) {
				System.out.println(i++ + ". Schleifendurchlauf des Threads");
				if (panel.isActive())
					panel.increaseIndex();
				else
					try {
						synchronized(panel) {
							panel.wait();
						}
					} catch (InterruptedException exc) {
						exc.printStackTrace();
					}
				try {
					Thread.sleep(400);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
 
Danke Andi,
hattest recht es funktioniert. War gestern Abend doch zu müde. Mein Thread läuft jetzt so wie ich es mir vorgestellt habe. Vielen Dank an Alle =) :applaus::toll:

Mfg Burny91
 

Zurück
Oben