[an error occurred while processing this directive]
PS5 Comments
1. Explain how the specification of our SimObject
datatype violates behavioral subtyping principles. Is there any way to
specify SimObject that doesn't violate the substitution
principle?
The
run method in
SimObject has a stronger
precondition than the
run method in
Runnable. The
supertype method specification has no precondition, but the subtype
precondition is
REQUIRES: this is initialized.
The run method in Runnable was specified with
EFFECTS: true to allow subtypes to do anything in the run
method, but because it has no precondition, subtypes cannot add
preconditions. To deal with this problem in SimObject we would
need to remove the precondition from run, and modify the
implementation so the constructor does the initialization. This would
make it harder to use the SimObject type, however.
2. Draw a module dependency diagram showing the design for the
walker simulation. It should include all the important classes listed
above, and show the subtyping ("is-a") and dependency ("has-a" or
"uses-a") relationships between the classes. Identify any circular
dependencies in the design and explain why they are necessary (or how
they could be avoided).
Here are the replationships:
| Class | Is-A (direct supertypes) | Has-a | Uses-a |
| Grid | java.lang.Object | SimObject | Coordinate |
| GridDisplay | java.lang.Object | Grid, SimObjectChooser | |
| Coordinate | java.lang.Object | | |
| Direction | java.lang.Object | | GenRandom |
| MobileSimObject | SimObject | | Grid |
| RandomWalker | MobileSimObject | | Grid,Direction |
| SimObject | java.lang.Object, Runnable | Grid | |
| Simulator | javax.swing.JPanel,
java.awt.event.ActionListener | GridDisplay | (Grid) |
| WalkerSimulator | java.lang.Object | | Grid, Simulator |
Note that the dependency of Simulator on Grid is a weakly uses
dependency — Simulator does not invoke any Grid methods, but
relies on the existence of a Grid type (but not its specification).
Here is the Modular Dependency Diagram:
One question the design should raise is why isn't
WalkerSimulator a subtype of
Simulator? At least from
the name, and the intended use, it would have made more sense for
WalkerSimulator to be a subtype of
Simulator.
There is a circular dependency between Grid and
SimObject. The Grid implementation depends on
SimObject since it uses an array of SimObjects to
store the grid objects and calls SimObject methods
(pauseObject, resumeObject). The SimObject
depends on the Grid since it has an instance variable that
stores the Grid this object is on and uses this in the
getNeighbors method. A cleaner design would avoid this
circular dependency, but doing this is tricky. It is very useful in the
SimObject implementation to have access to the containing
Grid. One way to avoid this would be to have a single
Grid object and make its methods static methods. This
would work for the WalkerSimulator since there is only one
grid, but it would limit other uses of the grid. The
getNeighbors method probably belongs in the Grid class
instead of the SimObject. If we moved it there, it would take
the location as a parameter, and return an enumeration of the
SimObjects neighboring that location.
3. Fix the code for RandomWalker.executeTurn so that
two MobileSimObjects will never go into the same square. Your fix
should not need to modify any code outside the
RandomWalker.executeTurn method. After your
fix, you should be able to run the simulation for as long as you want
without ever getting an exception for two objects entering the same
square.
We need to make sure the square we want to walk into does not become
occupied between the time we test it is empty, and when we move into
it. We can do this by locking the grid by putting a
synchronized (getGrid)
around the test and call to
setLocation:
public void executeTurn() throws RuntimeException {
inTurn = true;
Direction dir = Direction.randomDirection ();
int newrow = getRow () + dir.northerlyDirection ();
int newcol = getColumn () + dir.easterlyDirection ();
if (getGrid ().validLocation (newrow, newcol)) {
synchronized (getGrid ()) {
if (getGrid().isSquareEmpty (newrow, newcol)) {
delay (500);
setLocation (newrow, newcol);
}
}
...
4. Explain why the execution would get stuck if the threads
executed in the order described.
The execution gets stuck because the descartes object is
locked by the descartes thread, and at waiting to enter the
colleage.argue method for its colleague object,
plato. The plato object is locked by the plato
thread, and waiting to enter the colleague.argue method for
its colleague object, descarte. Neither thread can
proceeed, since argue is synchronized (it requires the
calling thread to hold the lock on the this object).
5. (Tricky, extra credit if you can answer this) Our new
Philosopher class now
has a race condition that could lead to a deadlock or run-time exception
(but it would never be apparent from our current main class). Explain
what the race condition is, and construct code that reveals it. Feel
free to insert sleep pauses as necessary to make it easier to reveal.
The race condition is that the colleague of the this object could change
between the
name.compareTo call in the this object's associated
thread and the
name.compareTo call in the colleague object
thread. Since
philsophize is not synchronized, the
(synchronized)
setColleague method could execute in another
thread while
philosophize is executing. Then, the wrong lock
would be grabbed! Instead of locking our current colleague, we would
grab the lock for our previous colleague and then call
colleague.argue (which is synchronized, and will wait for the
lock on our current colleague).
Even without the race condition, there are ways in which our solution
could still deadlock: we could have three philosophers who have
non-reflexive colleagues. Perhaps we should have a rep invariant that
requires colleague relationships be reflexive, and modify the
setColleague method to set the colleague's colleague to this.
It could also deadlock if two philosopher's have the same name. Then
compareTo returns 0 in both cases.
6. Fix the DrunkPhilosopher class to avoid the
deadlock. (Your fix should not involve removing or changing the
delay calls to make the deadlock less likely.
We need to remove the synchronized modifier on the
philosophize and executeTurn methods, and add code to
grab the locks is a set
order as with the Philosopher class.
7-9. Here are the applets:
- Mumps by Emily Lam and Rachel Phillips
- Snakes by Richard Hsu, Michael Lew, and Mike
Liu
- Thunderhammer by San Brunjes, Patrick
Harrison, and Kramer Sharp