1 - Korrekturinformation 2 - Konfligierende Philosophen 3

Werbung
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
Herunterladen