Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2015 Prof. Dr. Nitsche Übung 9 - Beispiellösung Threads In dieser Übung beschäftigen wir uns mit der Realisierung von Threads in Java. Aufgabe 1: Erzeugen und Starten von Threads a) Sei BankKunde eine von einer Klasse Kunde abgeleitete Klasse. Erweitern Sie die Klasse BankKunde derart, dass Bankkunden nebenläufig als Java-Threads operieren können. Als Thread-Operation soll 50-mal „Jetzt arbeitet <Kunde>“ ausgegeben werden, wobei <Kunde>der Name des Kunden ist. b) Erzeugen und starten Sie zwei BankKunden-Threads dagobert und donald. c) Analysieren Sie die Ausgabe des Programms. Starten Sie dazu das Programm mehrmals hintereinander. Ist die Ausgabe jeweils gleich, oder können Sie Unterschiede im Programmablauf erkennen? Hinweis: Sie können die Ausgabe auch zu „Jetzt arbeitet <Kunde> zum <i>-ten Mal“ abändern, um evtl. stärkere Effekte zu beobachten. Aufgabe 2: Nebenläufigkeit und Scheduling von Threads a) Betrachten Sie die folgende Klasse Konto. public class Konto { private int kontostand = 50; // Anfangsstand 50 Euro public int getKontoStand() { return this.kontostand; } public void abbuchen(int betrag) { int kontostand = this.kontostand; this.kontostand = kontostand - betrag; } } Analysieren Sie alle möglichen Abläufe, wenn 2 verschiedene Threads auf das gleiche Konto zugreifen wollen (d.h. nebenläufig die Methode abbuchen aufrufen), und jeweils 30 bzw. 40 Euro abheben wollen. Wie ist jeweils der Kontostand am Ende? Konto konto = new Konto(); // Anfangs-Kontostand = 50 thread1: konto.abbuchen( 30 ); || thread2: konto.abbuchen( 40 ); S. 1 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2013 thread1: konto.abbuchen( 30 ); || Prof. Dr. Nitsche Übung 9 – Beispiellösung thread2: konto.abbuchen( 40 ); b) Nicht ausgelastet? Analysieren Sie analog alle möglichen Abläufe und den daraus resultierenden Endbetrag des Kontostandes, wenn die Methode abbuchen wie folgt abgeändert wird: public void abbuchen(int betrag) { if (this.kontostand >= betrag) { int kontostand = this.kontostand; this.kontostand = kontostand - betrag; } else { throw new IllegalArgumentException( "Keine Kontoüberziehung erlaubt."); } } thread1: konto.abbuchen( 30 ); || thread2: konto.abbuchen( 40 ); c) Synchronisieren Sie die Aufrufe der Methode abbuchen durch das Schlüsselwort synchronized. Welche Abläufe und Kontoendstände sind hier möglich? public synchronized void abbuchen(int betrag) { if (this.kontostand >= betrag) { int kontostand = this.kontostand; this.kontostand = kontostand - betrag; } else { throw new IllegalArgumentException( "Keine Kontoüberziehung erlaubt."); } } thread1:konto.abbuchen( 30 ); || thread2:konto.abbuchen( 40 ); S. 2 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2013 Prof. Dr. Nitsche Übung 9 – Beispiellösung Aufgabe 3: Tic TacToe a) Stellen Sie das Spiel Tic TacToe fertig (vgl. Übung 8). Hinweis: Die Ereignis-Bearbeitung können Sie analog zu der in Übung 8 realisieren, die Spielobjekte (Kreise und Kreuze) können Sie aus den vorherigen Übungen übernehmen (siehe Webseite der Veranstaltung). • • Implementieren Sie eine Ereignis-Verarbeitung für die beiden Buttons. Reagieren Sie auf ferner auf Mausklicks im Zeichenpanel. b) Nicht ausgelastet? Erweitern Sie die Ereignis-Bearbeitung derart, dass bei Änderung der Fenstergröße automatisch auch die Spielobjekte (Kreise und Kreuze) in ihrer Größe angepasst werden. Hinweis: Über die Änderung der Größe einer GUI-Komponente können Sie durch die ComponentListener-Methode componentResized informieren lassen. Zusatz-Aufgabe 4: Bewegung von Objekten auf dem Bildschirm: Pong Das Spiel Pong gilt als das erste weltweit erfolgreiche Videospiel. Das Spielprinzip ähnelt dem des Tischtennis: Ein Punkt („Ball“) bewegt sich auf dem Bildschirm hin und her. Jeder der beiden Spieler steuert einen senkrechten Strich („Schläger“), den er mit einem Drehknopf (Paddle) nach oben und unten verschieben kann. Lässt man den „Ball“ am „Schläger“ vorbei, erhält der Gegner einen Punkt.[wikipedia.de] S. 3 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Bevor wir uns um das eigentliche Spielverhalten kümmern, sorgen wir zunächst für die Bewegung auf dem Bildschirm: Bewegen Sie einen Ball innerhalb des Fensters. Der Ball soll an den Rändern des Fensters „zurückprallen“ und dadurch ständig in Bewegung bleiben. (Im realen Spiel gilt nur der obere und untere Rand als Bande und die Seiten als „Aus“.) a) Erzeugen Sie zunächst eine Klasse PongPanel zum Zeichnen sowie eine Hauptklasse Pong, in der Sie ein Fenster mit dem Panel darstellen. b) Erzeugen Sie dann einen Ball, welcher das Interface games.basic.gameObjects.interfaces.Moveable implementiert. Hinweis: Ein Ball ist ein (farbig) ausgefüllter Kreis, der sich auf dem Bildschirm bewegen kann. Bevor Sie also alles neu implementieren, schauen Sie doch einfach nach, ob Sie da bereits bestehende Klassen (wieder-)verwenden können. c) Setzen Sie den Ball in Bewegung. Rufen Sie dazu ca. alle 30 Millisekunden die moveMethode des Ball-Objektes auf. (Sie können natürlich auch mehrere Bälle einsetzen) d) Ändern Sie nun die move-Methode derart, dass sich bei Berührung mit dem Rand des Panels jeweils die x- bzw. y-Richtung der Bewegung ändert. Hinweis: Dazu können Sie die Methoden reverseXDirection() bzw. reverseYDirection() von Moveable verwenden, sowie die Testfunktionen wie isBelowOf(y),isRightOf(x) etc. von GameObject. S. 4 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Alt (SS 2010): package exercises.gui.pong; import java.awt.Color; import java.awt.Graphics; import games.basic.gameObjects.moveable.AbstractMoveableGameObject; public class Ball extends AbstractMoveableGameObject { private int radius = 5; public Ball(int x, int y) { super(x, y); this.setDirection(3, 1); } @Override public void paintComponent(Graphics g) { g.setColor(Color.RED); g.fillOval(this.getX(), this.getY(), this.getWidth(), this.getHeight()); } @Override public int getWidth() { return radius * 2; } @Override public int getHeight() { return radius * 2; } } package exercises.gui.pong; import java.awt.Color; import java.awt.Graphics; import games.basic.gameObjects.moveable.AbstractMoveableGameObject; public class Paddle extends AbstractMoveableGameObject { private int width = 10; private int height = 30; public Paddle(int x, int y) { super(x, y); this.setDirection(0, 2); } S. 5 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung @Override public void paintComponent(Graphics g) { g.setColor(Color.BLUE); g.fillRect(this.getX(), this.getY(), this.getWidth(), this.getHeight()); } @Override public int getWidth() { return width; } @Override public int getHeight() { return height; } } package exercises.gui.pong; import games.basic.gameObjects.interfaces.GameObject; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import javax.swing.JPanel; public class PongPanelSimple extends JPanel { private PongSimple pong; public PongPanelSimple(PongSimple pong) { this.pong = pong; this.setFocusable(true); } @Override public void paintComponent(Graphics g) { g.setColor(Color.WHITE); g.fillRect(0,0,getWidth(),getHeight()); for( GameObject gameObj : pong.gameObjects) { gameObj.paintComponent(g); } // // // // g.setColor(Color.BLACK); Font bigFont = new Font("serif", Font.BOLD, 30); g.setFont(bigFont); g.drawString(""+pong.leftPoints, 40, 40); g.drawString(""+pong.rightPoints, getWidth()-80, 40); S. 6 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung for (int i = 0; i < 10; i++) { int lineLength = getHeight() / 20; g.drawLine(getWidth() / 2, (2 * i) *lineLength, getWidth()/2, (2 * i + 1)* lineLength); } } } package exercises.gui.pong; import games.basic.gameObjects.interfaces.MoveableGameObject; import java.util.ArrayList; import java.util.List; import javax.swing.JFrame; public class PongSimple { private JFrame frame; PongPanelSimple pongPanel; protected List<MoveableGameObject> gameObjects = new ArrayList<MoveableGameObject>(); protected Ball ball = null; protected Paddle leftPaddle; protected Paddle rightPaddle; protected int leftPoints = 0; protected int rightPoints = 0; public static void main(String args[]) { PongSimple pong = new PongSimple(); pong.work(); } private void work() { frame = new JFrame("Pong"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pongPanel = new PongPanelSimple(this); frame.add(pongPanel); frame.setSize(400, 300); frame.setVisible(true); ball = new Ball(22, frame.getHeight() / 2); gameObjects.add(ball); S. 7 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik // Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung pongPanel.addKeyListener( new PongKeyListener() ); while( true ) { try { Thread.sleep(29); } catch (InterruptedException e) { e.printStackTrace(); } move(); frame.repaint(); } } private void move() { System.out.println("frame: width = " + frame.getWidth() + ", height = " + frame.getHeight()); System.out.println("panel: width = " + pongPanel.getWidth() + ", height = " + pongPanel.getHeight()); System.out.println("ball: x = " + ball.getX() + ", y = " + ball.getY()); // // if (ball.isAboveOf(0) || ball.isBelowOf(pongPanel.getHeight())) { // falsch if ((ball.getY() < 0) || (ball.getY() + ball.getHeight()) > (pongPanel.getHeight())) { if (ball.touchesY(0) || ball.touchesY(pongPanel.getHeight())) { ball.reverseYDirection(); //ball.move(); } if (ball.isLeftOf(0) || ball.isRightOf(pongPanel.getWidth())) { // falsch if (ball.touchesX(0) || ball.touchesX(pongPanel.getWidth())) { ball.reverseXDirection(); //ball.move(); } // ball.move(); } } package exercises.gui.pong; import games.basic.gameObjects.interfaces.GameObject; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import javax.swing.JPanel; public class PongPanel extends JPanel { private Pong pong; S. 8 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung public PongPanel(Pong pong) { this.pong = pong; this.setFocusable(true); } @Override public void paintComponent(Graphics g) { g.setColor(Color.WHITE); g.fillRect(0,0,getWidth(),getHeight()); for( GameObject gameObj : pong.gameObjects) { gameObj.paintComponent(g); } g.setColor(Color.BLACK); Font bigFont = new Font("serif", Font.BOLD, 30); g.setFont(bigFont); g.drawString(""+pong.leftPoints, 40, 40); g.drawString(""+pong.rightPoints, getWidth()-80, 40); for (int i = 0; i < 10; i++) { int lineLength = getHeight() / 20; g.drawLine(getWidth() / 2, (2 * i) *lineLength, getWidth()/2, (2 * i + 1)* lineLength); } } } package exercises.gui.pong; import games.basic.gameObjects.interfaces.MoveableGameObject; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import javax.swing.JFrame; public class Pong { private JFrame frame; private PongPanel pongPanel; enum Player{left,right}; S. 9 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Player nextPlayer = Player.left; final char leftPlayerKey = 'a'; final char rightPlayerKey = 'l'; final char newBallKey = ' '; protected int leftPoints = 0; protected int rightPoints = 0; protected List<MoveableGameObject> gameObjects = new ArrayList<MoveableGameObject>(); private List<MoveableGameObject> paddles = new ArrayList<MoveableGameObject>(); protected Ball ball = null; protected Paddle leftPaddle; protected Paddle rightPaddle; public static void main(String args[]) { Pong pong = new Pong(); pong.work(); } private void work() { frame = new JFrame("Pong"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pongPanel = new PongPanel(this); frame.add(pongPanel); frame.setSize(400, 300); frame.setVisible(true); leftPaddle = new Paddle(10, frame.getHeight() / 2); rightPaddle = new Paddle(frame.getWidth() - 20, frame.getHeight() / 2); gameObjects.add(leftPaddle); gameObjects.add(rightPaddle); paddles.add(leftPaddle); paddles.add(rightPaddle); pongPanel.addKeyListener( new PongKeyListener() ); while( true ) { try { Thread.sleep(29); } catch (InterruptedException e) { e.printStackTrace(); } move(); frame.repaint(); } S. 10 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung } private void move() { rightPaddle.setX(pongPanel.getWidth()-20); // falsch if (ball != null) { if (ball.isAboveOf(0) || ball.isBelowOf(pongPanel.getHeight())) { // if (ball.touchesY(0) || ball.touchesY(pongPanel.getHeight())) { ball.reverseYDirection(); ball.move(); } if (ball.isLeftOf(0)) { // Ball vollständig verschwunden if (ball.touchesX(0)) { // Ball berührt nur Wand gameObjects.remove(ball); ball = null; rightPoints++; nextPlayer = Player.right; } else if (ball.isRightOf(pongPanel.getWidth())) { // Ball vollständig verschwunden // Wand } else if (ball.touchesX(pongPanel.getWidth())) { // // Ball berührt nur gameObjects.remove(ball); ball = null; leftPoints++; nextPlayer = Player.left; } else if // // // rightPaddle.getHeight())) // // // leftPaddle.getHeight()))) { (ball.touches(rightPaddle) || ball.touches(leftPaddle)) { ((ball.isRightOf( pongPanel.getWidth()-20) && ball.isBelowOf(rightPaddle.getY()) && ball.isAboveOf(rightPaddle.getY() + || (ball.isLeftOf(20) && ball.isBelowOf(leftPaddle.getY()) && ball.isAboveOf(leftPaddle.getY() + ball.reverseXDirection(); } } // // for (MoveableGameObject gameObj : gameObjects) { gameObj.move(); } for (MoveableGameObject paddle : paddles) { if (paddle.isAboveOf(0)) // falsch if (paddle.touchesY(0)) paddle.setY(0); if (paddle.isBelowOf( pongPanel.getHeight() - paddle.getHeight() )) S. 11 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung if (paddle.touchesY( pongPanel.getHeight() )) paddle.setY( pongPanel.getHeight() - paddle.getHeight() ); } } private class PongKeyListener implements KeyListener { @Override public void keyPressed(KeyEvent e) { if (e.getKeyChar() == leftPlayerKey) { leftPaddle.setYDirectionUp(); } if (e.getKeyChar() == rightPlayerKey){ rightPaddle.setYDirectionUp(); } } @Override public void keyReleased(KeyEvent e) { if (e.getKeyChar() == leftPlayerKey) { leftPaddle.setYDirectionDown(); } if (e.getKeyChar() == rightPlayerKey){ rightPaddle.setYDirectionDown(); } } @Override public void keyTyped(KeyEvent e) { if (ball == null && e.getKeyChar() == newBallKey) { switch (nextPlayer) { case right: ball = new Ball(pongPanel.getWidth() - 32, rightPaddle.getY() + 10); ball.reverseXDirection(); break; case left: ball = new Ball(22, leftPaddle.getY() + 10); break; } gameObjects.add(ball); } } } // class PongKeyListener } S. 12 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Alt – SS 2010 (TicTacToe) package exercises.gui.ttt; import java.awt.Color; import games.basic.gameObjects.CircularGameObject; public class Kreis extends CircularGameObject { private int feldX; private int feldY; private int breite; private int hoehe; public Kreis(int feldX, int feldY, int breite, int hoehe) { super(feldX * breite, feldY * hoehe, Math.min(breite, hoehe) / 2, Color.blue, 2); // System.out.println("Kreis: feldX = "+ feldX + ", feldY = " + feldY + ", breite = " + breite + ", hoehe = " + hoehe); this.feldX = feldX; this.feldY = feldY; this.breite = breite; this.hoehe = hoehe; } public void resize(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; this.setRadius(Math.min(breite, hoehe) / 2); this.setPos(feldX * breite, feldY * hoehe); } } package exercises.gui.ttt; import java.awt.Color; import games.basic.gameObjects.CrossGameObject; public class Kreuz extends CrossGameObject { private int feldX; private int feldY; private int breite; private int hoehe; public Kreuz(int feldX, int feldY, int breite, int hoehe) { super(feldX * breite, feldY * hoehe, breite, hoehe, Color.red, 2); S. 13 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung // System.out.println("Kreuz: feldX = "+ feldX + ", feldY = " + feldY + ", breite = " + breite + ", hoehe = " + hoehe); this.feldX = feldX; this.feldY = feldY; this.breite = breite; this.hoehe = hoehe; } public void resize(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; this.setWidth(breite); this.setHeight(hoehe); this.setPos(feldX * breite, feldY * hoehe); } } package exercises.gui.ttt; import javax.swing.JPanel; import javax.swing.border.LineBorder; import java.awt.Graphics; import java.awt.Color; import java.util.List; import java.util.LinkedList; import games.basic.gameObjects.interfaces.GameObject; public class TicPanel extends JPanel { private List<GameObject> gameObjects = new LinkedList<GameObject>(); public void addGameObject(GameObject gameObj) { gameObjects.add(gameObj); } public void clearGameObjects() { gameObjects.clear(); } @Override public void paintComponent(Graphics g) { int breite = this.getWidth(); int hoehe = this.getHeight(); g.setColor(Color.blue); S. 14 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung g.drawLine(0, hoehe/3, breite, hoehe/3); g.drawLine(0, hoehe/3*2, breite, hoehe/3*2); g.drawLine(breite/3, 0, breite/3, hoehe); g.drawLine(breite/3*2, 0, breite/3*2, hoehe); g.drawRect(0, 0, breite-1, hoehe-1); //this.setBorder( new LineBorder(Color.blue)); for( GameObject gameObj : gameObjects) { if (gameObj instanceof Kreuz) { ((Kreuz) gameObj).resize(breite / 3, hoehe / 3); } if (gameObj instanceof Kreis) { ((Kreis) gameObj).resize(breite / 3, hoehe / 3); } gameObj.paintComponent(g); } } } package exercises.gui.ttt; import games.basic.gameObjects.interfaces.GameObject; import java.awt.BorderLayout; import java.awt.Color; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; public class Tic implements ActionListener { public static void main(String[] args) { Tic tic = new Tic(); tic.run(); } private JFrame frame; private JButton newButton; private JButton endeButton; private TicPanel ticPanel; TicMouseListener ticMouseListener; S. 15 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung private boolean isKreuz = true; private Spieler winner = Spieler.LEER; // currently no winner enum Spieler { LEER, KREUZ, KREIS }; Spieler spielFeld[][] = new Spieler[3][3]; private void newGame() { for (int feldX = 0; feldX < 3; feldX++) { for (int feldY = 0; feldY < 3; feldY++) { spielFeld[feldX][feldY] = Spieler.LEER; } } ticPanel.clearGameObjects(); ticPanel.addMouseListener( ticMouseListener ); isKreuz = true; frame.repaint(); } private boolean isEnd() { // Prüfe auf Spielende for (int zeile = 0; zeile < 3; zeile++) { if (spielFeld[zeile][0] == spielFeld[zeile][1] && spielFeld[zeile][0] == spielFeld[zeile][2] && spielFeld[zeile][0] != Spieler.LEER) { winner = spielFeld[zeile][0]; // Gewinner return true; } } for (int spalte = 0; spalte < 3; spalte++) { if (spielFeld[0][spalte] == spielFeld[1][spalte] && spielFeld[0][spalte] == spielFeld[2][spalte] && spielFeld[0][spalte] != Spieler.LEER) { winner = spielFeld[0][spalte]; // Gewinner return true; } } // Diagonalen if (spielFeld[0][0] == spielFeld[1][1] && spielFeld[0][0] == spielFeld[2][2] && spielFeld[0][0] != Spieler.LEER) { winner = spielFeld[0][0]; // Gewinner return true; } if (spielFeld[0][2] == spielFeld[1][1] && spielFeld[0][2] == spielFeld[2][0] && spielFeld[0][2] != Spieler.LEER) { winner = spielFeld[0][2]; // Gewinner return true; } // Prüfe auf unentscheiden S. 16 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung winner = Spieler.LEER; for (int feldX = 0; feldX < 3; feldX++) { for (int feldY = 0; feldY < 3; feldY++) { if (spielFeld[feldX][feldY] == Spieler.LEER) return false; // Spiel noch nicht zuende } } return true; // Spiel zuende, alle Felder belegt } private int getFeldBreite() { return ticPanel.getWidth() / 3; } private int getFeldHoehe() { return ticPanel.getHeight() / 3; } public void run() { frame = new JFrame("Tic Tac Toe"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); newButton = new JButton("Neues Spiel"); endeButton = new JButton("Ende"); newButton.addActionListener(this); endeButton.addActionListener(this); JPanel buttonPanel = new JPanel(); ticPanel = new TicPanel(); ticPanel.setBackground( Color.darkGray); ticMouseListener = new TicMouseListener(); ticPanel.addMouseListener( ticMouseListener ); // // in newGame() newGame(); frame.add( BorderLayout.CENTER, ticPanel); frame.add( BorderLayout.SOUTH, buttonPanel); buttonPanel.add(newButton); buttonPanel.add(endeButton); frame.setVisible(true); frame.setSize(400, 400); frame.repaint(); } // // --- ActionListener ---------------------------------------------------@Override public void actionPerformed(ActionEvent e) { System.out.println("actionPerformed: " S. 17 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik // Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung + e.getActionCommand()); if (e.getSource() == newButton) { System.out.println("Neues Spiel starte ..."); this.newGame(); } else if (e.getSource() == endeButton){ System.out.println("Ende des Spiels ..."); System.exit(0); } } private int getBreite() { return ticPanel.getWidth(); } // --- MouseListener ---------------------------------------------------private class TicMouseListener implements MouseListener { // // // @Override public void mouseClicked(MouseEvent e) { System.out.println("mouseClicked"); int x = e.getX(); int y = e.getY(); System.out.println("Mausklick: x = " + x + ", y = " + y); int breite = ticPanel.getWidth(); int hoehe = ticPanel.getHeight(); int feldX = x / (breite/3); int feldY = y / (hoehe/3); System.out.println("Feld: x = " + feldX + ", y = " + feldY); if (spielFeld[feldX][feldY] == Spieler.LEER) { // nur, falls Feld noch nicht belegt GameObject temp; if(isKreuz) { temp = new Kreuz(feldX, feldY, breite / 3, hoehe / 3); spielFeld[feldX][feldY] = Spieler.KREUZ; isKreuz = false; } else { temp = new Kreis(feldX, feldY, breite / 3, hoehe / 3); spielFeld[feldX][feldY] = Spieler.KREIS; isKreuz = true; } ticPanel.addGameObject(temp); ticPanel.repaint(); if (isEnd()) { String msg = "Spiel beendet. "; if (winner == Spieler.LEER) msg += "Unentschieden"; else S. 18 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung msg+= "Gewinner = " + winner; System.out.println(msg); JOptionPane.showMessageDialog(frame, msg, "Ende", JOptionPane.INFORMATION_MESSAGE); // verhindere weitere Eingaben, bis neues Spiel startet ticPanel.removeMouseListener(ticMouseListener); } } else { System.out.println("Feld bereits belegt!"); } } @Override public void mouseEntered(MouseEvent e) { System.out.println("mouseEntered"); } // @Override public void mouseExited(MouseEvent e) { System.out.println("mouseExited"); } // @Override public void mousePressed(MouseEvent e) { System.out.println("mousePressed"); } // @Override public void mouseReleased(MouseEvent e) { System.out.println("mouseReleased"); } // } } S. 19 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Zusatz-Aufgabe 5: Synchronisation: Speisende Philosophen Es sitzen 5 Philosophen an einem runden Tisch. Zwischen zwei Philosophen liegt jeweils eine Gabel. Zum Essen benötigt jeder Philosoph zwei Gabeln. Offenkundig kann jede Gabel stets nur von höchstens einem Philosoph in der Hand gehalten werden. Ein Philosoph muss also ggf. warten, bis seine beiden Gabeln frei sind. a) Erstellen Sie eine Klasse Gabel. Diese soll – wie eine Semaphore – zwei Methoden void nehmen(Thread owner) und void freigeben() bereitstellen. Denken Sie daran, dass jede Gabel stets nur von einem Thread besessen werden kann, andere Threads beim Aufruf von nehmen folglich warten müssen, bis das Gabel-Objekt wieder freigegeben ist. b) Erstellen Sie ferner eine Klasse Philosoph, die als Thread operieren soll. Übergeben Sie den Philosophen Referenzen auf deren linke bzw. rechte Gabel und setzen Sie einen Namen für jeden Thread (z.B. „Philosoph 3“). Jeder Philosoph nimmt Sie sich seine linke Gabel, seine rechte Gabel, „speist“ 3 Sekunden, und gibt dann die Gabeln wieder frei. Geben Sie vor und nach jeder Aktion eine entsprechende Nachricht auf dem Bildschirm aus (jeweils mit Angabe des Namens des Philosophen). c) Erstellen Sie im Hauptprogramm 5 Gabel-Objekte und 5 Philosophen und starten Sie die Philosophen-Threads. Analysieren Sie den Ablauf des Programms. d) Ändern Sie die Klasse Philosoph derart ab, dass Sie zwischen der Aufnahme der linken und der Aufnahme der rechten Gabel jeweils eine Sekunde warten. Inwieweit ändert sich dadurch der Ablauf ihres Programms? e) Erweitern Sie ihr (Haupt-)programm derart, dass Sie durch Eingabe einer Zahl das entsprechenden Philosophen-Objekt unterbrechen können, d.h. dessen interrupt()-Methode aufrufen. Ein unterbrochener Thread soll evtl. belegte Gabeln freigeben und sich dann beenden. f) Nicht ausgelastet? Wie könnten Sie Ihr Programm abändern, sodass keine Verklemmungen (Deadlocks) auftreten können? Hinweis: Suchen Sie – z.B. in der Literatur – zunächst einen verklemmungsfreien Algorithmus, bevor Sie ihn in Java realisieren. S. 20 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Philosopher0: package exercises.threads.philosopher0; public class Gabel { private boolean belegt = false; public boolean isBelegt() { return belegt; } // request public synchronized void nehmen() { while (belegt) { try { wait(); } catch (InterruptedException e) { System.out.println("Gabel.nehmen: InterruptedException " + e.getMessage()); e.printStackTrace(); } } // while belegt = true; } // release public synchronized void freigeben() { belegt = false; notify(); } } package exercises.threads.philosopher0; public class Philosoph extends Thread { private int number; private Gabel links; private Gabel rechts; private boolean hatLinkeGabel = false; private boolean hatRechteGabel = false; public Philosoph(int number, Gabel links, Gabel rechts) { this.number = number; this.links = links; this.rechts = rechts; } private void sleep(int ms) { if (!this.isInterrupted()) { try { Thread.sleep(ms); } catch (InterruptedException e) { System.out.println("Philosoph.sleep: InterruptedException " + e.getMessage()); S. 21 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung this.interrupt(); } } } public void run() { System.out.println("Gestartet: " + this); // nimm linke Gabel while (this.links.isBelegt() && !this.isInterrupted()) { System.out.println(this + " wartet auf linke Gabel"); sleep(5000); } if (!this.links.isBelegt() && !this.isInterrupted()) { this.links.nehmen(); this.hatLinkeGabel = true; } System.out.println(this); // nimm rechte Gabel sleep(1000); // --> sleep bewirkt deadlock while (this.rechts.isBelegt() && !this.isInterrupted()) { System.out.println(this + " und wartet auf rechte Gabel"); sleep(5000); } if (!this.rechts.isBelegt() && !this.isInterrupted()) { this.rechts.nehmen(); this.hatRechteGabel = true; } System.out.println(this); // essen sleep(1000); sleep(35000); System.out.println(this + "--> ist nun satt und gibt Gabeln // frei"); // Gabeln freigeben this.rechts.freigeben(); this.hatRechteGabel = false; System.out.println(this); this.links.freigeben(); this.hatLinkeGabel = false; System.out.println("Beendet: " + this); } @Override public String toString() { String val = "Philosoph " + number; if (this.hatLinkeGabel) { val += " hat linke Gabel"; } if (this.hatRechteGabel) { val += " hat rechte Gabel"; } if (hatLinkeGabel && hatRechteGabel) { S. 22 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung val += " und isst"; } return val; } } package exercises.threads.philosopher0; import java.util.Scanner; public class Main { private static final int N = 5; public static void main(String[] args) { Gabel[] gabeln = new Gabel[N]; for (int i = 0; i < N; i++) { gabeln[i] = new Gabel(); } Philosoph[] phils = new Philosoph[N]; for (int i = 0; i < N; i++) { phils[i] = new Philosoph(i, gabeln[i], gabeln[(i+1) % N]); phils[i].start(); } while (true) { Scanner scan = new Scanner(System.in); int i = scan.nextInt(); phils[i].interrupt(); synchronized (phils[i]) { phils[i].notify(); } } } } Philosopher2: package exercises.threads.philosopher2; public class Gabel { private Thread owner = null; public boolean isBelegt() { return owner != null; } public Thread getOwner() { return this.owner; } // request public synchronized void nehmen(Thread owner) { S. 23 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung while (this.isBelegt() && !owner.isInterrupted()) { try { wait(); } catch (InterruptedException e) { System.out.println("Gabel.nehmen: InterruptedException " + e.getMessage()); e.printStackTrace(); owner.interrupt(); } } // while // belegt = true; this.owner = owner; } // // release public synchronized void freigeben() { belegt = false; owner = null; notify(); } } package exercises.threads.philosopher2; public class Philosoph extends Thread { private Gabel linkeGabel; private Gabel rechteGabel; public Philosoph(int number, Gabel linkeGabel, Gabel rechteGabel) { this.setName("Philosoph " + number); this.linkeGabel = linkeGabel; this.rechteGabel = rechteGabel; } private void sleep(int ms) { if (!this.isInterrupted()) { try { Thread.sleep(ms); } catch (InterruptedException e) { System.out.println("Philosoph2.sleep: InterruptedException " + e.getMessage()); this.interrupt(); } } } public void run() { System.out.println("Gestartet: " + this); // nimm linke Gabel System.out.println(this + " wartet auf linke Gabel"); if (!this.isInterrupted()) { this.linkeGabel.nehmen(this); } System.out.println(this); // nimm rechte Gabel sleep(1000); // --> sleep bewirkt deadlock S. 24 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung System.out.println(this + " und wartet auf rechte Gabel"); if (!this.isInterrupted()) { this.rechteGabel.nehmen(this); } System.out.println(this); // essen sleep(1000); sleep(5000); System.out.println(this + "--> ist nun satt und gibt Gabeln // frei"); // Gabeln freigeben System.out.println(this + " und gibt rechte Gabel frei"); this.rechteGabel.freigeben(); System.out.println(this); System.out.println(this + " gibt linke Gabel frei"); this.linkeGabel.freigeben(); System.out.println("Beendet: " + this); } @Override public String toString() { String val = this.getName(); if (this.hatGabel(linkeGabel)) { val += " hat linke Gabel"; } if (this.hatGabel(rechteGabel)) { val += " hat rechte Gabel"; } if (hatGabel(linkeGabel) && hatGabel(rechteGabel)) { val += " und isst"; } return val; } private boolean hatGabel(Gabel g) { return g.isBelegt() && g.getOwner() == this; } } S. 25 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Zusatz-Aufgabe 6 (für Java-Experten): Java-Rätsel a) Das folgende Programm iteriert durch eine Liste von int-Arrays und ermittelt, wie viele der Arrays eine bestimmte Eigenschaft aufweisen. Was gibt das Programm aus? public class Loop { public static void main(String[] args) { int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 }, { 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } }; int successCount = 0; try { int i = 0; while (true) { if (thirdElementIsThree(tests[i++])) successCount++; } } catch (ArrayIndexOutOfBoundsException e) { // No more tests to process } System.out.println(successCount); } private static boolean thirdElementIsThree(int[] a) { return a.length >= 3 & a[2] == 3; } } Ausgabe: 0 Lösung: • • • Wir erwarten, dass die Ausgabe 2 sein sollte: Es gibt genau zwei Teil-Arrays, die als 3. Element eine 3 besitzen ({1, 2, 3} und {1, 2, 3, 4}). Allerdings erhalten wir eine 0. Warum? Zunächst sollte die Iteration über alle Array-Elemente nicht durch eine Exception (ArrayIndexOutOfBoundsException) beendet werden, sondern als normale Schleife definiert werden. (Ist vom Code besser verständlich, und auch deutlich schneller). for (int i = 0; i < tests.length; i++) { if (thirdElementIsThree(tests[i++])) successCount++; } ... private static boolean thirdElementIsThree(int[] a) { return a.length >= 3 & a[2] == 3; } S. 26 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik • Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Dann sehen wir, dass es plötzlich eine Exception gibt, die vorher „versteckt“ mit abgefangen wurde: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2 at Loop_Korrektur_v1.thirdElementIsThree(Loop_Korrektur_v1.java:16 ) at Loop_Korrektur_v1.main(Loop_Korrektur_v1.java:9) • • • Grund ist der Vergleich in der Methode thirdElementIsThree: Der Operator & ergibt eine bitweise und-Verknüpfung, und berechnet dazu beide Argumente. Folglich wird auch dann auf a[2] zurückgegriffen, wenn das int-Array weniger als 3 Elemente enthält (bei {1, 2} und {1} ). Daran erkennen wir auch, warum unser Ausgangsprogramm als Ergebnis eine 0 ausgegeben hat: Das Array {6,5,4,3,2,1} hat keine 3 an 3. Stelle (d.h. successCount bleibt 0), und beim nächsten Array {1,2} wird bereits die Schleife wegen einer ArrayIndexOutOfBoundsException beendet. Korrigieren wir in der Methode thirdElementIsThree den Operator zum logischen Und-Operator &&, erhalten wir das Gewünschte. Dieser Operator wird „lazy“ ausgewertet, d.h. das zweite Argument wird nur betrachtet, wenn das erste Argument true ist, d.h. das Array mindestens 3 Elemente umfasst und der Zugriff auf a[2] damit nicht fehlschlägt. private static boolean thirdElementIsThree(int[] a) { return a.length >= 3 && a[2] == 3; } • Allerdings gibt es jetzt die Ausgabe: 1 • • Grund ist die doppelte Erhöhung von i in der Schleife, d.h. wir Testen lediglich die Arrays {6, 5, 4, 3, 2, 1}, {1, 2, 3} und {1}. Dies korrigieren wir wie folgt: for (int i = 0; i < tests.length; i++) { if (thirdElementIsThree(tests[i])) successCount++; } • Das korrekte Programm liefert nun – wie erwartet: 2 S. 27 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik • Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Anmerkung: Statt in der for-Schleife auf alle Array-Elemente per Index zuzugreifen, können wir ab Java 5 auch die for-each-Schleife verwenden, die für alle Datenstrukturen mit einem Iterator funktioniert: for (int[] test : tests) { if (thirdElementIsThree(test)) successCount++; } b) Dieses Programm berechnet und speichert eine Summe in einer Klasse und gibt sie in einer anderen Klasse aus. Was gibt das Programm aus? Hinweis: Die Summe aller Zahlen von 1 bis n beträgt n(n+1)/2. class Cache { static { initializeIfNecessary(); } private static int sum; public static int getSum() { initializeIfNecessary(); return sum; } private static boolean initialized = false; private static synchronized void initializeIfNecessary() { if (!initialized) { for (int i = 0; i < 100; i++) { sum += i; } initialized = true; } } } public class Client { public static void main(String[] args) { System.out.println(Cache.getSum()); } } Ausgabe: 9900 Erklärung: • Summe wäre eigentlich 100*99/2 = 4950. S. 28 / 29 Hochschule Niederrhein Fachbereich 03 Bachelor Informatik • Grundlagen der Java Programmierung SS 2012 Prof. Dr. Nitsche Übung 8 - Beispiellösung Aber die Summe wird zweimal berechnet. class Cache_Lsg { private static final int SUM = computeSum(); private static int computeSum() { int result = 0; for (int i = 0; i < 100; i++) { result += i; } return result; } public static int getSum() { return SUM; } } public class Client_Lsg { public static void main(String[] args) { System.out.println(Cache_Lsg.getSum()); } } c) Manchmal ist es nützlich zu zählen, wie viele Instanzen von einer Klasse erzeugt wurden. Dies wird typischerweise durch eine statische, private Zählervariable realisiert, die im Konstruktor inkrementiert wird. Im folgenden Programm demonstriert die Creature-Klasse dieses Prinzip, während die Creator-Klasse die Anzahl der erzeugten Creature-Instanzen ausgibt. Was gibt das Programm aus? public class Creator { public static void main(String[] args) { for (int i = 0; i < 100; i++) Creature creature = new Creature(); System.out.println(Creature.numCreated()); } } class Creature { private static long numCreated = 0; public Creature() { numCreated++; } public static long numCreated() { return numCreated; } } S. 29 / 29