1 - Korrekturinformation 2 - Konfligierende Philosophen Implementierungsfehler 1. Fehler in Klasse Stick, Zeile 10 Beim Zurücklegen des Sticks fehlt die Anweisung isUsed = false, da der Stick sonst nie von einem anderen Philosophen genutzt werden kann. Richtig wäre also: public synchronized void put() { isUsed = false; notify(); } 2. Fehler in Klasse Stick, Zeile 14 Innerhalb des try-Blocks muss die wait()-Anweisung in einer whileSchleife stehen, da sonst der andere Philosoph den Stick zurücklegen und sofort wieder nehmen könnte und ab dann 2 Philosophen denselben Stick nutzen würden. Richtig wäre also: public synchronized void take() { while (isUsed) { try { wait(); } catch (InterruptedException e) {} } isUsed = true; } 3. Fehler in Klasse Philosopher, Zeile 17 - 23 Das wait auf dem linken Stick macht zum Einen keinen Sinn, da das anschließende left.take() unter Umständen ebenfalls warten würde. Zum anderen ist bereits klar, dass der Stick nicht verwendet werden kann. Es muss einen alternativen Fall geben, der durch else eingeleitet wird. Richtig wäre also: right.take(); if (left.isUsed()) { right.put(); } else { left.take(); System.out.println("Eating."); } left.put(); right.put(); 3 - Talk Client-Interface 1 2 import java.rmi.Remote; import java.rmi.RemoteException; 3 4 /** This is an interface for a simple RMI talk client */ 1 5 public interface TalkClient extends Remote { 6 /** Connect to the talk client */ public void connect(TalkClient other) throws RemoteException; 7 8 9 /** Hang up a connection */ public void bye() throws RemoteException; 10 11 12 /** Send a message to the talk client */ public void send(String msg) throws RemoteException; 13 14 15 16 } Client-Implementierung 1 2 3 4 5 import import import import import java.rmi.RemoteException; java.rmi.registry.LocateRegistry; java.rmi.registry.Registry; java.rmi.server.UnicastRemoteObject; java.util.Scanner; 6 7 8 /** This implements a simple Chat Client via RMI */ public class TalkClientImpl extends UnicastRemoteObject implements TalkClient { 9 10 private static final long serialVersionUID = -9116560583694172171L; 11 12 13 /** The other client */ private TalkClient other; 14 15 16 17 18 /** Create a new client with a connection peer */ public TalkClientImpl(TalkClient tc) throws RemoteException { this.other = tc; } 19 20 21 22 23 24 /** Receive a call */ public synchronized void connect(TalkClient other) { this.other = other; notify(); } 25 26 27 28 29 30 /** Receive a good bye message */ public synchronized void bye() { other = null; System.out.println("*** Partner left the talk. ***"); } 31 32 33 34 35 /** Receive a message from the server and print it to the output */ public void send(String msg) { System.out.println(msg); } 36 37 38 39 40 41 /** Establish a connection */ private synchronized void waitForOther() throws RemoteException { if (other == null) { System.out.println("*** Waiting for talk partner. ***"); try { 2 42 43 44 45 46 47 48 } wait(); } catch (InterruptedException _) { } } else { other.connect(this); } 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 /** A simple read-print-loop which reads messages from the input */ public void talk() throws RemoteException { waitForOther(); System.out.println("Welcome to talk, type ':q' to quit."); try (Scanner in = new Scanner(System.in)) { String msg; while (other != null) { msg = in.nextLine(); synchronized (this) { if (other == null) { ; } else if (msg.equals(":q")) { other.bye(); other = null; } else { other.send(msg); } } } } } 71 72 73 74 75 76 77 78 79 80 81 82 83 /** Create a new registry or connect to the existing one */ private static Registry getOrCreateRegistry() { try { return LocateRegistry.createRegistry(Registry.REGISTRY_PORT); } catch (RemoteException _) { try { return LocateRegistry.getRegistry(); } catch (RemoteException __) { return null; } } } 84 85 86 /** Start a new talk client */ public static void main(String[] args) throws Exception { 87 88 89 Registry reg; TalkClientImpl t; 90 91 92 93 94 95 96 97 98 switch (args.length) { case 1: reg = getOrCreateRegistry(); t = new TalkClientImpl(null); reg.bind(args[0], t); t.talk(); reg.unbind(args[0]); break; 3 99 100 101 102 103 104 105 106 107 108 109 110 111 } 112 case 2: reg = LocateRegistry.getRegistry(args[1]); t = new TalkClientImpl((TalkClient) reg.lookup(args[0])); t.talk(); break; default: System.err.println("Usage:"); System.err.println("To receive a call: java TalkClientImpl <user>"); System.err .println("To call someone : java TalkClientImpl <user> <host>"); System.exit(1); } System.exit(0); 113 114 } 4 - Beschränkter Puffer public class BufferN<T> { private T[] array; private private private private int int int int read; write; count; capacity; @SuppressWarnings("unchecked") public BufferN(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException( "Capacity must be greater than zero"); } read = 0; write = 0; count = 0; this.capacity = capacity; array = (T[]) new Object[capacity]; } public synchronized T take() throws InterruptedException { while (count == 0) { wait(); } T v = array[read]; read = (read + 1) % capacity; if (count-- == capacity) { notifyAll(); } return v; } public synchronized void put(T v) throws InterruptedException { while (count == capacity) { 4 } } wait(); } array[write] = v; write = (write + 1) % capacity; if (count++ == 0) { notifyAll(); } public synchronized boolean isEmpty() throws InterruptedException { return count == 0; } 5 - Erweiterter Buffer1 import java.util.concurrent.TimeoutException; /** * Single element buffer with synchronization. * * @param <E> * Type of the element */ public class ExtBuffer<E> { // element + empty flag private E content; private boolean empty; // synchronization objects private Object r = new Object(); private Object w = new Object(); public ExtBuffer() { empty = true; } public ExtBuffer(E content) { this.content = content; empty = false; } /** * take the element from the buffer; suspends on an empty buffer. * * @return element of the buffer * @throws InterruptedException */ public E take() throws InterruptedException { synchronized (r) { while (empty) { r.wait(); } synchronized (w) { 5 } } } empty = true; w.notify(); return content; /** * put an element into the buffer; suspends on a full buffer * * @param o * Object to put into * @throws InterruptedException */ public void put(E o) throws InterruptedException { synchronized (w) { while (!empty) { w.wait(); } synchronized (r) { content = o; empty = false; r.notify(); } } } /** * Return whether the buffer is empty * * @return true if empty */ public boolean isEmpty() { return empty; } /** * Read the element from the buffer without emptying it; suspends on an * empty buffer. * * @return element of the buffer * @throws InterruptedException */ public E read() throws InterruptedException { synchronized (r) { while (empty) { r.wait(); } synchronized (w) { // This notify is important to allow succeeding read/take // operations r.notify(); return content; } } } 6 /** * Try to put an element into the buffer; succeeds only for an empty buffer * * @param elem * Element to put into * @return true if successful */ public boolean tryPut(E elem) { synchronized (w) { if (empty) { synchronized (r) { content = elem; empty = false; r.notify(); return true; } } else { return false; } } } /** * Overwrite the element in the buffer, even if the buffer is empty * * @param elem * Element to overwrite with */ public void overwrite(E elem) { synchronized (w) { content = elem; if (empty) { synchronized (r) { empty = false; r.notify(); } } } } /** * take with timeout. The timeout mechanism has to be handcrafted as there * is no way to detect whether a wait() was left because of a timeout or a * notify(). * * @param timeout * Maximum time to wait in milliseconds * @return * @throws InterruptedException * @throws TimeoutException * if a timeout occurred */ public E take(long timeout) throws InterruptedException, TimeoutException { long start = System.currentTimeMillis(); synchronized (r) { 7 } } } while (empty) { long remaining = timeout - System.currentTimeMillis() + start; if (remaining > 0) { r.wait(remaining); } else { throw new TimeoutException("time out"); } } synchronized (w) { empty = true; w.notify(); return content; } Klassen Ähnliche Implementierungen sind java.util.concurrent.ArrayBlockingQueue bzw. java.util.concurrent.Li Da beide Varianten mehrere Elemente aufnehmen können, entspricht ein Buffer1 einer Instanz mit der Kapazität 1. Methoden Die Methoden put und take haben jeweils eine gleichnamige Entsprechung, isEmpty entspricht dem Aufruf size() == 0. Die Methode tryPut des Buffer1 entspricht der Methode offer, die Methoden read und overwrite des Buffer1 haben jedoch keine direkte Entsprechung. Implementierung Während der Buffer1 zur Synchronisierung zwei Synchronisationsobjekte und synchronized-Blöcke nutzt, werden in der Java-API ReentrantLocks sowie Conditions genutzt. 6 - RMI-Chat Server-Interface 1 2 3 import java.rmi.Remote; import java.rmi.RemoteException; import java.util.List; 4 5 6 7 8 /** * This is an interface for a simple RMI chat server. */ public interface ChatServer extends Remote { 9 10 public final String RMI_NAME = "chatserver"; 11 12 13 14 15 16 17 18 19 20 /** * Register a client at the server * * @param c * the client to register * @exception RemoteException * if an error occurs */ public boolean register(ChatClient c, String name) throws RemoteException; 21 8 /** * Retrieve the collection of users * * @return the list of users currently logged in * @throws RemoteException */ public List<String> getUsers() throws RemoteException; 22 23 24 25 26 27 28 29 /** * Remove a client from the server * * @param c * the client to remove * @exception RemoteException * if an error occurs */ public void logout(ChatClient c) throws RemoteException; 30 31 32 33 34 35 36 37 38 39 /** * Send a message to all connected clients * * @param msg * the message to send * @exception RemoteException * if an error occurs */ public void send(String msg) throws RemoteException; 40 41 42 43 44 45 46 47 48 49 50 } Server-Implementierung 1 2 3 4 5 6 7 8 9 import import import import import import import import import java.rmi.RemoteException; java.rmi.registry.LocateRegistry; java.rmi.registry.Registry; java.rmi.server.UnicastRemoteObject; java.util.ArrayList; java.util.HashMap; java.util.List; java.util.Map; java.util.Map.Entry; 10 11 12 13 14 15 /** * This implements a simple chat server via rmi. The server has a map of clients * and can send messages to them. */ public class ChatServerImpl extends UnicastRemoteObject implements ChatServer { 16 17 private static final long serialVersionUID = -687973037052959819L; 18 19 20 /** The map of connected clients */ private Map<ChatClient, String> clients; 21 22 23 24 25 /** * Creates a new {@code ChatServerImpl} instance. * * @exception RemoteException 9 26 27 28 29 30 * if an error occurs */ public ChatServerImpl() throws RemoteException { clients = new HashMap<>(); } 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 /** * Register a client at the server and returns if the login was successful * * @param c * the client to register * @param name * the name of the client * @return true if the login was successful * @exception RemoteException * if an error occurs */ public boolean register(ChatClient c, String name) throws RemoteException { synchronized (clients) { if (clients.containsValue(name)) { return false; } else { clients.put(c, name); send(name + " joined the chat."); return true; } } } 54 55 56 57 58 59 60 61 62 63 64 65 /** * Retrieve the collection of all users * * @exception RemoteException * if an error occurs */ public List<String> getUsers() throws RemoteException { synchronized (clients) { return new ArrayList<>(clients.values()); } } 66 67 68 69 70 71 72 73 74 75 76 77 /** * Send a message to all connected clients. Remove a client, if an error * occurs while sending to it. * * @param msg * the message to send * @exception RemoteException * if an error occurs */ public void send(String msg) throws RemoteException { Map<ChatClient, String> failed = new HashMap<>(); 78 79 80 81 82 synchronized (clients) { for (Entry<ChatClient, String> e : clients.entrySet()) { try { e.getKey().send(msg); 10 } catch (RemoteException _) { failed.put(e.getKey(), e.getValue()); } 83 84 85 86 87 88 89 } 90 } for (ChatClient c : failed.keySet()) { clients.remove(c); } 91 92 93 94 } 95 for (String name : failed.values()) { send("The connection to " + name + " was reset."); } 96 /** * Removes a client. * * @param c * the client to remove * @exception RemoteException * if an error occurs */ public void logout(ChatClient c) throws RemoteException { synchronized (clients) { String name = clients.get(c); clients.remove(c); send(name + " left the chat."); } 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 } 112 113 /** Starts the server using a new RMI-registry */ public static void main(String[] args) { try { LocateRegistry.createRegistry(Registry.REGISTRY_PORT).rebind( RMI_NAME, new ChatServerImpl()); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } 114 115 116 117 118 119 120 121 122 123 124 125 } Client-Interface 1 2 import java.rmi.Remote; import java.rmi.RemoteException; 3 4 5 6 7 /** * This is an interface for a simple RMI chat client */ public interface ChatClient extends Remote { 8 9 10 11 /** * Receive a message from the server and print it * 11 * @exception RemoteException * if an error occurs */ public void send(String msg) throws RemoteException; 12 13 14 15 16 17 } Client-Implementierung 1 2 3 4 5 import import import import import java.rmi.RemoteException; java.rmi.registry.LocateRegistry; java.rmi.registry.Registry; java.rmi.server.UnicastRemoteObject; java.util.Scanner; 6 7 8 /** This implements a simple Chat Client via RMI */ public class ChatClientImpl extends UnicastRemoteObject implements ChatClient { 9 10 private static final long serialVersionUID = -9116560583694172171L; 11 12 13 /** The chat server the client is connected to */ private ChatServer cs; 14 15 16 /** The name of the client */ private String name; 17 18 19 /** A flag whether we are logged in */ private boolean loggedIn; 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 /** * Creates a new {@code ChatClient} instance. * * @param name * The name of the client * @param cs * the chat server to connect to * @exception RemoteException * if an error occurs */ public ChatClientImpl(String name, ChatServer cs) throws RemoteException { this.name = name; this.cs = cs; loggedIn = false; } 36 37 38 39 40 41 42 /** A simple read-print-loop which reads messages from the input */ public void run() throws RemoteException { boolean registered = cs.register(this, name); if (registered) { loggedIn = true; System.out.println(cs.getUsers()); 43 44 45 46 47 48 try (Scanner in = new Scanner(System.in)) { String msg; while (loggedIn) { msg = in.nextLine(); if (msg.equals(":q")) { 12 logout(); } else { cs.send(name + ": " + msg); } 49 50 51 52 53 54 55 56 57 } 58 } } } else { System.out.println("Could not register: user name is occupied"); } 59 /** Receive a message from the server and print it to the output */ public void send(String msg) { System.out.println(msg); } 60 61 62 63 64 /** Remove the client from the server and exit the program */ private void logout() throws RemoteException { cs.logout(this); loggedIn = false; } 65 66 67 68 69 70 /** * Starts a new client and connects to a given rmi registry * * @param args * The command line arguments. args[0] is the username registry * and args[1] the optional host of the server */ public static void main(String[] args) { 71 72 73 74 75 76 77 78 79 if (args.length == 0) { System.err.println("Usage:"); System.err.println("java ChatClient <username> [<host>]"); System.exit(1); } 80 81 82 83 84 85 String name = args[0]; String host = args.length == 1 ? "localhost" : args[1]; 86 87 88 89 90 91 92 93 94 95 96 97 } 98 try { Registry reg = LocateRegistry.getRegistry(host); ChatServer cs = (ChatServer) reg.lookup(ChatServer.RMI_NAME); new ChatClientImpl(name, cs).run(); System.exit(0); } catch (Exception e) { e.printStackTrace(); System.exit(1); } 99 100 } Der Nachrichtenoverhead bei RMI ist relativ groß, da nicht nur die Nachrichten an sich, sondern ganze Objekte serialisiert und dann versendet werden. Damit ist RMI unter Umständen keine Alternative zu TCP-basierten Programmen mit eigenem Kommunikationsprotokoll, aber für unseren Chatserver ist die Performanz allemal ausreichend. 13 7 - Verbesserter Counter Wir unterscheiden zunächst 3 Arten von Nachrichten: start, stop und tick: 1 public class CounterMessage { 2 public enum MessageType { COUNTER_START, COUNTER_STOP, COUNTER_TICK } 3 4 5 6 public static CounterMessage start(int value) { return new CounterMessage(MessageType.COUNTER_START, value); } 7 8 9 10 public static CounterMessage stop(int value) { return new CounterMessage(MessageType.COUNTER_STOP, value); } 11 12 13 14 public static CounterMessage tick(int value) { return new CounterMessage(MessageType.COUNTER_TICK, value); } 15 16 17 18 private MessageType type; private int value; 19 20 21 private CounterMessage(MessageType type, int value) { this.type = type; this.value = value; } 22 23 24 25 26 public MessageType getType() { return type; } 27 28 29 30 public int getValue() { return value; } 31 32 33 34 35 } Der Counter zählt wie gehabt und informiert seine Beobachter: 1 2 import java.util.Observable; import java.util.Observer; 3 4 public class Counter extends Observable implements Runnable { 5 6 7 8 9 private private private private int value; long delay; boolean running; int num; 10 11 12 /** Static variable to create unique counter names */ private static int numOfCounters = 0; 13 14 15 16 public Counter(int value, long delay) { this.value = value; this.delay = delay; 14 17 18 19 } this.running = false; this.num = ++numOfCounters; 20 21 22 23 24 25 26 27 28 29 30 31 32 /** Count from 0 up to infinity */ public void run() { while (running) { this.value++; this.setChanged(); this.notifyObservers(CounterMessage.tick(value)); try { Thread.sleep(delay); } catch (InterruptedException e) { } } } 33 34 35 36 37 38 39 public void start() { this.running = true; new Thread(this).start(); this.setChanged(); this.notifyObservers(CounterMessage.start(value)); } 40 41 42 43 44 45 public void stop() { this.running = false; this.setChanged(); this.notifyObservers(CounterMessage.stop(value)); } 46 47 48 49 50 51 52 53 public Counter copy() { Counter counter = new Counter(this.value, this.delay); if (this.running) { counter.start(); } return counter; } 54 55 56 57 public int getNumber() { return num; } 58 59 60 61 public int getCount() { return value; } 62 63 64 65 public boolean isRunning() { return running; } 66 67 68 69 70 71 72 73 /** When no GUI observes us we can stop counting */ @Override public void deleteObserver(Observer o) { super.deleteObserver(o); if (this.countObservers() == 0) { stop(); } 15 } 74 75 76 } Die CounterGUI wiederum beobachtet einen Counter: 1 2 3 4 5 6 7 8 import import import import import import import import java.awt.Container; java.awt.Font; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.event.WindowAdapter; java.awt.event.WindowEvent; java.util.Observable; java.util.Observer; import import import import import javax.swing.BoxLayout; javax.swing.JButton; javax.swing.JFrame; javax.swing.JLabel; javax.swing.JPanel; 9 10 11 12 13 14 15 16 17 public class CounterGUI extends WindowAdapter implements Observer, ActionListener { 18 19 20 /** Our counter object */ private Counter counter; 21 22 23 /** The frame */ private JFrame frame; 24 25 26 /** The label */ private JLabel label; 27 28 29 /** The start/stop button */ private JButton startStop; 30 31 32 33 /** Create a new Counter GUI */ public CounterGUI(Counter counter) { this.counter = counter; 34 35 36 37 38 // Create the window frame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.addWindowListener(this); 39 40 41 Container content = frame.getContentPane(); content.setLayout(new BoxLayout(content, BoxLayout.PAGE_AXIS)); 42 43 44 45 46 // Add the label label = new JLabel(); label.setFont(new Font(null, Font.BOLD, 100)); content.add(label); 47 48 49 50 // Add button(s) JPanel buttons = new JPanel(); content.add(buttons); 51 52 startStop = new JButton(); 16 setButtonTextCommand(startStop, counter.isRunning() ? "stop" : "start"); startStop.addActionListener(this); buttons.add(startStop); 53 54 55 56 JButton copy = new JButton(); setButtonTextCommand(copy, "copy"); copy.addActionListener(this); buttons.add(copy); 57 58 59 60 61 JButton clone = new JButton(); setButtonTextCommand(clone, "clone"); clone.addActionListener(this); buttons.add(clone); 62 63 64 65 66 JButton close = new JButton(); setButtonTextCommand(close, "close"); close.addActionListener(this); buttons.add(close); 67 68 69 70 71 counter.addObserver(this); label.setText(new Integer(counter.getCount()).toString()); frame.setTitle("Counter " + counter.getNumber()); 72 73 74 75 76 77 78 } frame.pack(); frame.setVisible(true); 79 80 81 82 83 84 /** Helper to set the button caption and the action name */ public void setButtonTextCommand(JButton button, String text) { button.setText(text); button.setActionCommand(text); } 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 /** * When the counter updates its state, we get notified by a call to this * method. */ public void update(Observable o, Object value) { if (!(value instanceof CounterMessage)) { throw new IllegalArgumentException("CounterMessage expected"); } CounterMessage msg = (CounterMessage) value; switch (msg.getType()) { case COUNTER_START: start(); break; case COUNTER_STOP: stop(); break; case COUNTER_TICK: tick(msg.getValue()); } } 106 107 108 109 /** Process a start message. */ public void start() { setButtonTextCommand(startStop, "stop"); 17 110 } 111 112 113 114 115 /** Process a stop message. */ public void stop() { setButtonTextCommand(startStop, "start"); } 116 117 118 119 120 121 122 123 /** Process a tick message. */ public void tick(Integer value) { if (value == null) { System.exit(0); } label.setText(value.toString()); } 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 /** This method gets called whenever a button is pressed. */ public void actionPerformed(ActionEvent e) { switch (e.getActionCommand()) { case "start": counter.start(); break; case "stop": counter.stop(); break; case "copy": new CounterGUI(counter.copy()); break; case "clone": new CounterGUI(counter); break; case "close": close(); break; default: ; } } 147 148 149 150 151 152 /** Invoked when the close button is pressed. */ public void close() { counter.deleteObserver(this); frame.dispose(); } 153 154 155 156 157 158 /** Invoked when the user closes the window. */ @Override public void windowClosed(WindowEvent e) { close(); } 159 160 161 162 163 164 165 166 /** Main method to start some counters */ public static void main(String[] args) { for (String arg : args) { Counter counter = new Counter(0, new Long(arg)); new CounterGUI(counter); counter.start(); } 18 167 168 } } 19