0% found this document useful (0 votes)
112 views16 pages

Final07 Sol

This document appears to be a final exam for a course on computer science (CS 242). It contains several short answer and multiple choice questions covering topics like: - Alpha-conversion and why it's useful in macro preprocessors - The differences between overloading, polymorphism, and dynamic method lookup - Subtyping vs inheritance - Prolog terms that unify - Outputs from a Prolog query - Implementing lazy vs eager evaluation strategies in ML - Ownership types for preventing memory errors in programs with explicit new and delete

Uploaded by

Gobara Dhan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
112 views16 pages

Final07 Sol

This document appears to be a final exam for a course on computer science (CS 242). It contains several short answer and multiple choice questions covering topics like: - Alpha-conversion and why it's useful in macro preprocessors - The differences between overloading, polymorphism, and dynamic method lookup - Subtyping vs inheritance - Prolog terms that unify - Outputs from a Prolog query - Implementing lazy vs eager evaluation strategies in ML - Ownership types for preventing memory errors in programs with explicit new and delete

Uploaded by

Gobara Dhan
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 16

CS 242. Autumn 2007.

December 10.
CS 242 Final Exam
1. (18 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Short Answer
Answer each question in a few words or phrases, or select the correct answers if multiple
choices are offered.
(a) (3 points) What is alpha-conversion and why is it useful in a macro pre-processor
(an algorithm that expands macro denitions in a program)?
Answer: Alpha-conversion renames bound variables. It is important when expand-
ing macros that contain declarations, since it may be necessary to rename bound
variables inside the macro to avoid capturing free variables that occur in macro
arguments.
(b) (3 points) What is the difference between overloading and polymorphism?
Answer: Overloading gives one symbol many implementations (many algorithms).
Polymorphism allows one algorithm to be applied to many types of arguments.
(c) (3 points) What is the difference between overloading and dynamic method lookup?
Answer: Overloading is resolved at compile time. The compiler determines which
algorithm is appropriate, based on context, and compiles the chosen algorithm into
the resulting executable code. Dynamic method lookup is a run-time mechanism for
selecting the code to execute, typically based on the run-time value of a pointer to
an object.
(d) (3 points) Explain the difference between subtyping and inheritance.
Answer: Subtyping is a relation between interfaces, allowing objects of one type to
be used in place of objects of another. Inheritance is a relation between implementa-
tions, allowing the implementation of one class to be reused in the implementation
of another.
(e) (3 points) Which of these pairs of Prolog terms unify? (Circle all correct answers.)
i. f(X) ; g(Y)
ii. f(X) ; f(h(Z))
iii. arrow(int, int) ; arrow(X, Y)
iv. g(X, Y, Z) ; g(Y, Z, X)
Answer: All except the rst pair.
(f) (3 points) Which of the following outputs will the following Prolog query produce?
(Circle the correct answer.)
member(X, [_|Xs]) :- member(X, Xs).
member(X, [X|_]).
?- member(Z, [1,2,3]).
1
i. Z = 1;
Z = 2;
Z = 3;
No
ii. Z = 1;
Z = 3;
Z = 2;
No
iii. Z = 3;
Z = 2;
Z = 1;
No
iv. Z = 3;
Z = 1;
Z = 2;
No
Answer: iii.
2. (10 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lazy and Eager Evaluation
Function calls in most languages, including ML, Lisp, C, and Java, are evaluated by rst
evaluating the arguments to the function and then performing the function call. This is
called eager evaluation. In the functional language Haskell, function arguments are not
evaluated until they are neededarguments are not unevaluated until the program ac-
tually uses their value. This is called lazy evaluation. In lazy evaluation, if an argument
is never used, then it is never evaluated. .
Lazy evaluation can be implemented using thunks, which are no-argument functions
that are called when an expression needs to be evaluated. For example, a lazy expression
f(e) can be compiled to eager code that passes a thunk for e to the function body of f.
If f(x) contains an expression x+1, for example, then the function can call the thunk
for e to obtain its value, and then it can add 1 to it.
Consider the following code fragment, written in a ML-like functional language.
let times3 x = x + x + x
times3 (5
*
7)
Let us think about how we can implement the eager and lazy evaluation strategies for
this code using the (eager) ML language.
Because ML is an eager language, we dont have to make any changes at all to imple-
ment an eager evaluation strategy in ML. Simply running the code as written will rst
compute 5
*
7 = 35 and then call times3 with the argument 35.
We can implement lazy evaluation in ML by writing the following code:
let times3 x_thunk = (x_thunk ()) + (x_thunk ()) + (x_thunk ())
times3 (fun () -> 5
*
7)
2
The times3 function expects a thunk that produces the value if its argument, and calls
the function where the value of the argument is needed. Rather than receiving the value
35, instead times3 now takes a function that evaluates 5
*
7 when needed.
(a) (2 points) Haskell is a pure functional language. What does this imply about the
three values of x in any call to times3?
Answer: The three occurrences of the same expression will have the same value.
(b) (3 points) Since times3(x) contains three occurrences of x, the thunk for the func-
tion argument will be called three times. What trick could you use in a Haskell
compiler to improve the efciency of the compiled code for times3? (Hint: think
about how futures worked in Lisp.)
Answer: The thunk stores its value the rst time it is computed. Haskell creates
a thunk to represent unevaluated expressions. Once a thunks value is computed,
then Haskell replaces it with the computed value. This way, each arguments value
will be computed only once, or perhaps not at all. (With eager evaluation, each
arguments value is computed exactly once.)
(c) (2 points) Translate the following lazy functional code into (eager) ML.
fun g x y = if x < 0 then 0 else y
ANSWER:
fun g x________ y________ =
if ______________ < 0 then 0 else ______________
Answer:
(d) (3 points) Does the choice of evaluation strategy change the eventual output of a
pure functional program? More specically, if f is a function and e1 e2 ... en
are arguments, is the value of f(e1, e2, ..., en) the same under eager and
lazy evaluation strategies, assuming that all are written in a pure functional lan-
guage? If so, explain briey, if not, provide a counterexample and brief explanation.
(Hint: Consider the example functions used in this question, and all possible argu-
ments they might be applied to.)
Answer:
3. (14 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ownership Types
Ownership types are a typing concept that is used to prevent memory management er-
rors by determining which pointer owns the object it points to. In this approach, every
object is pointed to by one, and only one, owning pointer. An object can be reachable via
many pointers, but the owning pointer is the only pointer that can be used to delete the
object. Ownership may be transferred to another pointer, but since every object must be
deleted exactly once, ownership cannot be cloned or lost.
In an ownership type system, the type of a pointer consists of its ordinary type (speci-
fying the type of object it points to) and an additional owning bit at every point in the
program. If the owning bit is set for a given pointer at a given line of code, then the
3
pointer owns the object it points to. While owning bits could be set explicitly by pro-
grammer annotation, it is more common to use an inference algorithm to compute the
owning bit for each pointer and program point.
Owning bits must obey the following rules:
When a new object is created, then the owning bit of the pointer to which it is
assigned is set to 1.
To delete an object via a pointer, that pointer must have an owning bit with value
1. After deletion, the pointers owning bit is set to 0.
Assignments of the form x = y where y is an owning pointer can either transfer
ownership from y to x, in which case x becomes an owning pointer and y becomes
a non-owning pointer, or can leave the owning bits of both x and y unchanged.
It is an error to lose the value of an owning pointer, either by overwriting it or by
reaching the end of the scope in which the pointer is declared.
We say a program is well-typed under the ownership pointer scheme if there is some
assignment of owning bits to each pointer variable at each program point satisfying the
rules given above.
In the rest of the question, we use an imaginary variant of Java with both explicit new
and delete primitives instead of a garbage collector.
Here is an example of a well-typed program, showing the owning bits for variables u and
v after each line of code:
Code u z
1: void foo() {
2: Object u, z; 0 0
3: u = new Object; 1 0
4: z = u; 0 1
5: delete z; 0 0
6: }
4
(a) (4 points) The following code is well-typed under the ownership type system. In the
space provided below, compute an owning bit assignment for each program variable
after each line in the program. The rst two are done for you.
Code u v w
1: void foo() {
2: Object u, v, w; 0 0 0
3: u = new Object; 1 0 0
4: v = u;
5: w = u;
6: u = new Object;
7: w = u;
8: delete v;
9: delete u;
10: }
(b) (2 points) Ownership types prevent two kinds of program errors. One error is mem-
ory leakage, which is prevented by the rules that prevent overwriting or losing an
owning pointer. Describe a second kind of a memory-management error that occurs
with unrestricted new and delete but is prevented by ownership pointers.
Answer: double frees
(c) (2 points) Ownership types are used to approximate some desirable program cor-
rectness properties. In at most a sentence or two, describe the ideal necessary and
sufcient condition for calling delete on an object that prevents memory errors?
(Hint: If you are stuck, read the next part of the question.)
Answer:
(d) (2 points) How do garbage collectors usually ensure that the condition you stated in
the last part of this problem holds before collecting an object from the heap?
Answer: Unreachable and collected at most once
(e) (2 points) Do ownership pointers completely solve the problem of safe deletion of
objects? That is, in a program that passes the ownership type rules, are we guaran-
teed that no illegal memory accesses can occur? You may assume that the language
is otherwise type-safe.
Answer: Yes
(f) (2 points) What are some advantages and disadvantages of explicit memory manage-
ment using ownership types, as compared to garbage collection? List and explain
at least one advantage and one disadvantage.
Answer: Advantages include: prevent memory leaks, prevent double-free, no dy-
namic cost. Disadvantages include: conservative, does not prevent dangling point-
ers, probably requires annotation from the user.
5
4. (9 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parameter passing comparison
For the following Algol-like program, write the number printed by running the program
under each of the listed parameter passing mechanisms. In pass-by-value-result, also
called call-by-value-result and copy-in/copy-out, parameters are passed by value, with
an added twist. More specically, suppose a function f with a pass-by-value-result pa-
rameter u is called with actual parameter v. The activation record for f will contain a
location for formal parameter u that is initialized to the R-value of v. Within the body of
f, the identier u is treated as an assignable variable. On return from the call to f, the
actual parameter v is assigned the R-value of u.
begin
integer i;
procedure pass ( x, y );
integer x, y; // types of the formal parameters
begin
y := x
*
2;
x := x - 3;
i := i
*
x;
y := x
end
i := 5;
pass (i, i);
print i
end
(a) (3 points) pass-by-value
Answer: The program prints 10 because the third assignment of the procedure sets
global variable i to 5 (5 3) = 10.
(b) (3 points) pass-by-reference
Answer: The program prints 49 because i,x,y are aliases and the procedure sets
i := 5
*
2; i:= i-3; i:= i
*
i.
(c) (3 points) pass-by-value/result
Answer: The program prints 7 because the return from pass sets i to y and the
procedure sets y to 7.
5. (14 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JavaScript Objects
In designing JavaScript, Brendan Eich intended to include a few, hard-working prim-
itives in the language. While JavaScript does not provide object orientation directly,
JavaScripts other language features can be combined to achieve many of the goals
of object orientation. These techniques allow programmers to build large, Web 2.0
JavaScript applications that run in web browsers.
(a) (3 points) One method of retrotting object-oriented features onto JavaScript uses
closures. A JavaScript programmer wrote the following code to implement Point
with move and toString methods and Colored Points with move, darken, and toString
methods:
function Point(x, y) {
var obj = new Object;
6
obj.move = function(dx, dy) {
x += dx;
y += dy;
};
obj.toString = function() {
return "[Point with x=" + x + " and y=" + y + "]";
};
return obj;
}
function ColoredPoint(x, y, color) {
var obj = Point(x, y);
obj.darken = function (tint) {
color += tint;
};
obj.toString = function() {
return "[ColoredPoint with x=" + x + ", y=" + y +
", and color=" + color + "]";
};
return obj;
}
p = Point(3, 4);
p.move(1, 2);
This code has a bug. Write three lines of JavaScript that exhibit the bug. Explain
briey below or in the space to the right.
________________________________
________________________________
________________________________
Answer: Colored Points, the x and y accessed by move are different from the x,y
used by toString.
(b) (4 points) Which object-oriented language features does this translation achieve (as
written)? Circle all that apply and justify your choices with one sentence each:
Dynamic lookup
Answer: Circled. The toString method invoked depends on the which object is
present at run time.
Encapsulation
Answer: Circled. The x, y, color member variables are a closure and can
only be accessed via public methods. Note that encapsulation can be broken us-
ing parent in Firefox.
Inheritance
Answer: Circled. ColoredPoint inherits the implementation of move from Point.
Subtyping
Answer: Circled. As in SmallTalk, one object can be used in place of another as
long as it implements the appropriate methods.
7
(c) (1 point) Similar to parent in Self, every JavaScript object has a prototype ob-
ject. When looking up a member, for example while evaluating x.y, the interpreter
rst looks for y in the object x. If it cant nd y, then it repeats this search in xs
prototype. Complete the following analogy:
Answer: Prototype is to object as access link is to activation record.
(d) (2 points) Most object-oriented JavaScript code uses prototypes and is written in the
following style:
function Point(x, y) {
this.init(x, y);
}
Point.prototype.init = function(x, y) {
this.x = x;
this.y = y;
};
Point.prototype.move = function(dx, dy) {
this.x += dx;
this.y += dy;
};
Point.prototype.toString = function() {
return "[Point with x=" + this.x + " and y=" + this.y + "]";
};
var p = new Point(3, 5);
p.move(1, 2);
The object Point.prototype is the prototype object shared by all objects created
via new Point. Assigning methods to this object makes those methods available
on all such objects. Is the procedure for calling move more similar to method invo-
cation in SmallTalk, C++, or Java? Explain your choice by describing the invocation
procedure.
Answer: SmallTalk. First, the interpreter searches for move in object p. Fail-
ing that, it searches Point.prototype, which acts as the method dictionary in
SmallTalk.
(e) (2 points) Object prototypes can also be used to simulate inheritance. Below, a
JavaScript programmer has attempted to dene ColoredPoint by inheriting from
Point.
function ColoredPoint(x, y, color) {
this.init(x, y, color);
}
ColoredPoint.prototype = Point.prototype; // <--- There is a bug here.
ColoredPoint.prototype.init = function(x, y, color) {
Point.prototype.init.call(this, x, y); // Calls Points init on "this"
this.color = color;
};
ColoredPoint.prototype.darken = function (tint) {
this.color += tint;
};
ColoredPoint.prototype.toString = function() {
return "[ColoredPoint with x=" + this.x + ", y=" + this.y +
8
", and color=" + this.color + "]";
};
var cp = new ColoredPoint(4, 2, 127)
What bug is caused by the line indicated? (Hint: Try drawing a box-and-pointer
diagram of the objects involved.)
Answer: The darken method has been added to Point objects because Points and
ColoredPoints share a single prototype.
(f) (2 points) The problem can be xed using only the fragment of JavaScript presented
in this question, by inserting the following three lines in place of the buggy line.
function foo() { }
foo.prototype = Point.prototype;
ColoredPoint.prototype = new foo();
Explain what this accomplishes and how. (Hint: Remember SmallTalks run-time
data structures.)
Answer:
6. (14 points) . . . . . . . . . Java generic wildcard upper and lower bounds
Java generics can be written using type parameters or wildcards, which are like anony-
mous type parameters. Type parameters can be given an upper bound by writing <T extends C>,
which means that the type parameter T must be a subtype of class C. In other words, the
type parameter T has upper bound C. The same upper bound syntax can be used for wild-
cards, as in <? extends C>. For example, an object of List<? extends Number> is
a list that contains objects which extend the Number class. For example, the list could
be List<Float> or List<Number>. Reading an element from such a list is guaranteed
to return a Number, but writing to the list is not generally allowed.
Wildcards can also have lower bound constraints, written using super instead of extends.
A constraint <? super C> means that the wildcard type must be a supertype of class
C. For example, an object of List<? super Number> could be a List<Number> or
List<Object>. Reading from such a list returns objects of type Object, but any object
of type Number can be added to the list.
This question asks about generic versions of a simple function (static method) that reads
elements from one list and adds them to another. Here is sample non-generic code, in
case this is useful reference in looking at the generic code below.
public static void addAll_nonGeneric(List src, List dest) {
for (Object o : src) {
dest.add(o);
}
}
List listOfNumbers = new ArrayList();
List listOfIntegers = new ArrayList();
...
addAll_nonGeneric(listOfIntegers, listOfNumbers);
(a) (2 points) The simplest generic version of the addAll method uses an unconstrained
type parameter and no wildcards.
9
public static <T> void addAll_0(List<T> src, List<T> dest) {
for (T o : src) {
dest.add(o);
}
}
Suppose that we declare
List<Number> listOfNumbers = new ArrayList<Number>();
List<Integer> listOfIntegers = new ArrayList<Integer>();
and call addAll_0(listOfIntegers, listOfNumbers). Will this call compile
or will a type error be reported at compile time? Explain briey.
Answer: The compiler will try to nd a type T with List < Number >= T = List <
Integer >. Since this is not possible, the compiler will report a compile-time typ-
ing error. Because generics are not covariant or contravariant, subtyping between
Number and Integer has no effect.
(b) (2 points) With listOfIntegers and listOfNumbers dened as in the previous
part of this question, will the call addAll_1(listOfIntegers, listOfNumbers)
compile, where addAll_1 is dened as follows:
public static <T> void addAll_1(List<? extends T> src, List<T> dest) {
for (T o : src) {
dest.add(o);
}
}
Explain briey.
Answer: This compiles without error because Integer is a subtype of T=Number.
(c) (2 points) With listOfIntegers and listOfNumbers dened as in the previous
part of this question, will the call addAll_2(listOfIntegers, listOfNumbers)
compile, where addAll_2 is dened as follows:
public static <T> void addAll_2(List<T> src, List<? super T> dest) {
for (T o : src) {
dest.add(o);
}
}
Explain briey.
Answer: This compiles without error because Number is a supertype of T=Integer.
(d) (2 points) Suppose we want to change our function so that in addition to adding
elements of one list to another list, we also return the last element added. Code for
this static method is written below. Fill in each of the blanks with type parameters,
wildcards, and/or constraints so that the code will type-check at compile time and
the result of a call to addAll_3 will have the best possible type.
public static <T> T addAll_3(List< _______ > src, List< _______ > dest) {
T last;
for (T o : src) {
dest.add(o);
last = o;
10
}
return last;
}
Answer: A good answer is to ll in the blanks with < T > and <? super T >. It is
also reasonable to use <? extends T > and <? super T >.
(e) (2 points) In the code in part 6d above, suppose src has type List<R> and dest
has type List<S>. What subtype/supertype relationships between R, S, and T are
needed for the body of the method to be type-correct?
Answer: Either R = T <: S or R <: T <: S, depending on your answer to the
previous part of the question.
(f) (2 points) In your solution to part 6d above, how do the constraints you wrote guar-
antee the subtype/supertype relationships between R, S, and T you listed in part
6e?
Answer: R <: T because R=T and T <: S because of the <? super T > constraint.
Or, if you used <? extends T > above then this constraint on the type of src causes
R <: T directly.
(g) (2 points) Suppose that your friend comes across the following general coding sug-
gestion on the web and asks you to explain. What can you tell your friend to explain
the connection between the coding advice and principles of subtyping, showing off
your understanding gained from CS242?
Get and Put Principle: use an extends wildcard when you only GET values
out of a structure, use a super wildcard when you only PUT values in a
structure, and dont use a wildcard when you both get and put.
Answer: An operation that reads objects from a data structure will work when
objects are from any subtype of the type used to operate on these objects: reading
is covariant. Conversely, and writing into a data structure is possible with the re-
ceiving data structure expects objects from any supertype; writing is contravariant.
If both get and put operations are used on the same structure, then using a struc-
ture with supertype elements would violate the typing requirements for get, and
a structure requiring subtype objects would violate typing requirements for put.
Since neither subtyping nor supertyping is correct for both kinds of operations, an
exact type match is required, and wildcards should not be used.
7. (11 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C++ Multiple Inheritance
This question asks about multiple inheritance in C++. Consider the following (buggy)
C++ program for logging I/O operations on a touch screen. The intended purpose of this
program is to log all operations in the sequence they occur. Each operation should have
a unique, sequentially-increasing operation number.
class Device {
public:
int numOps; // Counts number of operations performed.
Device() { numOps = 0; }
};
class InputDevice: public Device {
public:
11
int getX() {
printf("Op %d: getX", ++numOps);
return x;
}
int getY() {
printf("Op %d: getY", ++numOps);
return y;
}
private:
int x, y;
};
class OutputDevice: public Device {
public:
void drawLine(int fromX, int fromY, int toX, int toY) {
... //line drawing code
printf("Op %d: drawLine", ++numOps);
}
};
class TouchScreen: public InputDevice, public OutputDevice { };
int main() {
TouchScreen s;
int x, y;
...
// Draw cross hairs on current touch position.
while(true) {
x = s.getX();
y = s.getY();
s.drawLine(x-5, y, x+5, y);
s.drawLine(x, y-5, x, y+5);
}
...
}
12
(a) (2 points) What is printed by the program after the rst iteration of the while loop
in main()?
Answer:
Op 1: getX
Op 2: getY
Op 1: drawLine
Op 2: drawLine
(b) (2 points) Change two lines in this program so that it prints the expected output:
Answer: Change denition of InputDevice and OutputDevice to
class InputDevice: public virtual Device {...}
class OutputDevice: public virtual Device {...}
(c) (3 points) As we have seen, diamond inheritance often leads to unintended complica-
tions. Here is an alternate logging scheme that does not use diamond inheritance:
1: ...
2: class InputDevice {
3: public:
4: virtual void log(int &i) { // Note: parameter i is passed by reference.
5: printf("Op %d: Input", ++i);
6: }
7: };
8: class OutputDevice {
9: public:
10: virtual void log(int &i) { // Note: parameter i is passed by reference.
11: printf("Op %d: Output", ++i);
12: }
13: };
14: class TouchScreen: public InputDevice, public OutputDevice {};
15: int main() {
16: int i = 0;
17: TouchScreen t;
18: t.log(i);
19: ...
20: }
This program has a compile time error related to multiple inheritance. Describe
what prevents the program from compiling correctly and suggest a x (there are
multiple possibilities; pick just one).
Answer: The error is on line t.log(i) because of ambiguous method access. Ways
to x the problem (only one required): let TouchScreen implement method log; dis-
ambiguate t.log(i) as ((InputDevice)t).log(); or t.InputDevice::log();
or create superclass pointer: InputDevice
*
i = &t; i->log();
(d) (2 points) Java disallows multiple inheritance as shown in the C++ example above.
However, it offers a exible subtyping mechanism to provide subtyping from mul-
tiple supertypes. Fill in the blanks below to create a concrete class TouchScreen in
Java, that is a both a subtype of InputDevice and OutputDevice.
public_______________ Device {...}
public_______________ InputDevice ______________________________{...}
13
public_______________ OutputDevice _____________________________{...}
public_______________ TouchScreen___________________________________{...}
Answer:
public interface Device{...}
public interface InputDevice extends Device {...}
public interface OutputDevice extends Device {...}
public class TouchScreen implements InputDevice, OutputDevice {...}
(e) (2 points) In Java, using types Device, InputDevice, OutputDevice, and TouchScreen,
could you implement a logging scheme that counts I/O operations using a counter in
Device similar to the one scheme shown in (a)? If yes, explain how in one sentence.
If no, explain why not.
Answer: No, interfaces cannot declare data members.
8. (10 points) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java Concurrency
NPRs Car Talk radio show, in which Click and Clack, the Tappet brothers, give opinion-
ated advice on all kinds of automotive ailments, wants to expand online. Their Computer
Hardware Specialist, C. Colin Backslash, is trying to write a serverside Java program
that can reply to user queries about car problems with auto-generated back and forth di-
alogue between the two show hosts. Early on in the project, Colin encounters a puzzling
concurrency problem and asks for your help. His code exhibits unpredictable deadlocks:
public class CarTalkGuy extends Thread {
private String name;
private CarTalkGuy brother;
private static SummerIntern gladysOvernow = new SummerIntern();
public CarTalkGuy(String name) {
super();
this.name=name;
}
public void setBrother(CarTalkGuy b) {
brother=b;
}
public synchronized void dispenseAdvice() {
String brokenPart = gladysOvernow.researchAnswer();
System.out.print(name+": Clearly, your car needs a "+brokenPart+".");
brother.add2Cents();
}
public synchronized void add2Cents() {
System.out.print(name+": And remember: dont drive like my brother.");
brother.haveLastWord();
}
public synchronized void haveLastWord() {
System.out.println(name+": No, dont drive like
*
my
*
brother!");
}
public void run() {
dispenseAdvice();
}
public static void main(String[] args) {
14
CarTalkGuy click = new CarTalkGuy("Click");
CarTalkGuy clack = new CarTalkGuy("Clack");
click.setBrother(clack);
clack.setBrother(click);
click.start();
clack.start();
}
}
When the program executes successfully, it produces output like the following:
Click: Clearly, your car needs a new radiator. Clack: And remember:
dont drive like my brother. Click: No, dont drive like
*
my
*
brother!
Clack: Clearly, your car needs a right blinker bulb. Click: And
remember: dont drive like my brother. Clack: No, dont drive like
*
my
*
brother!
Reminder: In Java, you can create a thread by extending the Thread class and overrid-
ing the Thread.run() method. A call to Thread.start() returns immediately in the
calling thread and executes Thread.run() in a newly created thread.
(a) (2 points) Locks protecting synchronized objects in Java are reentrant: threads that
already own a particular lock are allowed to acquire the lock more than once. Which
function calls, during a successful execution as given above, are only possible be-
cause of reentrant locks?
Answer: The calls to haveLastWord() the thread arriving at this method al-
ready acquired the same lock when entering dispenseAdvice(), from which it
has not yet returned.
(b) (2 points) Sometimes the given program deadlocks, especially for complicated cases
when the call to researchAnswer() may take a while to return. Explain, in terms
of threads, locks, and synchronized objects, why deadlock may occur here. Your
answer should not make any assumptions about class SummerIntern.
Answer: This is an instance of the mutually recursive monitor problem. Thread
1 is in clicks dispenseAdvice() method when it gets switched out. Thread 2
enters clacks dispenseAdvice() method. Without loss of generality, assume that
Thread 1 rst reaches the call to brother.add2Cents(). Thread 1 has to wait
because Thread 2 still holds the lock on clack. However, as Thread 1 waits, it retains
the lock on click. Now Thread 2 resumes and it reaches brother.add2Cents().
But because Thread 1 still holds the lock on click, it cannot enter the method either
- we are deadlocked. Calling Thread.sleep() increases the chance that Thread 1
will get interrupted before it can enter brother.add2Cents().
(c) (2 points) What will the program have output when the deadlock occurs?
Answer: Click says: Clearly, your car needs a new radiator.
Clack says: Clearly, your car needs a new transmission.
Click and Clack can be interchanged. Students can ll in any completion after your
car needs a.
15
(d) (2 points) After some thought, Colin suggests taking some pressure off the SummerIntern
by passing in a hint after calling researchAnswer() and presents the following
code, in which the SummerIntern waits until the hint is given:
public class SummerIntern {
String hint=null;
public synchronized String researchAnswer() {
//...
while (hint==null) wait();
//...lookup and return answer
}
public synchronized void provideHint(String hint) {
this.hint=hint;
notify();
}
}
To provide the hint, class CarTalkGuy gets an extra synchronized method :
public synchronized void provideHint() {
gladysOvernow.provideHint("look under the hood");
}
Also, the last two lines of main are changed so that click will call researchAnswer()
in one thread and also provide the hint in another thread:
click.start();
click.provideHint();
This change avoids the previous deadlock problem, since Clacks thread is never
started. To Colins surprise though, the new program sometimes deadlocks without
printing anything. In which lines of code do Clicks threads get stuck now during a
deadlock?
Answer: Thread 1 gets stuck in line while (hint==null) wait();. Thread 2
gets stuck in the call to gladysOvernow.provideHint(...)
(e) (2 points) Explain in one paragraph why the threads are deadlocked.
Answer: This is an instance of the nested monitor lockout problem. When getAdvice()
runs rst in thread 1, that thread acquires the lock for click, then acquires the
lock for gladysOvernow, but has to wait in gladysOvernow.getAdvice(). Thread
1 thus releases the lock for gladysOvernow, but retains the lock on click. How-
ever, the only way to notify the waiting thread is through click.provideHint(),
which cannot be entered by any other thread because thread 1 still holds the lock.
We are deadlocked again.
16

You might also like