CMSI 673 Final Exam Answers ------------------------------------------------------------------------------ PROBLEM 1 In my WinAPI DiningPhilosophers program, what happens if a philosopher dies while holding a chopstick? Suppose that we wanted it to be the case that if a philosopher does die while holding a chopstick, her neighbor can pick it up and continue with her thinking-eating life cycle. If my program already does this, explain why; if not, rewrite the necessary parts of the code so that it does. Answer: The program already does this. The neighbor's call to WaitForSingleObject() will acquire the mutex and return WAIT_ABANDONED so the Philosopher::run method unblocks. In this case the mutex isn't protecting any transactional behavior, so the abandonment causes no erroneous behavior at all. ------------------------------------------------------------------------------ PROBLEM 2 Suppose a Windows thread t1 has stupidly entered CRITICAL_SECTION c and while inside calls WaitForSingleObject(t2, INFINITE) on a thread t2 which is blocked because it is trying to enter c. Can a third thread t3 break the deadlock by doing a "leave" or a "delete" on c? Why or why not? Answer: Both ideas are problematic. If t3 calls LeaveCriticalSection on c while t1 owns c, then we get an error state, which, according to Microsoft, could result in t2 being blocked for ever. Therefore the deadlock might be broken or it might not, but the technique does NOT break it for sure. If t3 calls DeleteCriticalSection on c while t1 owns c, then the state of t2 is undefined, so again, this method does NOT reliably break the deadlock. ------------------------------------------------------------------------------ PROBLEM 3 Under what situations would you use an ArrayBlockingQueue for a ThreadPoolExecutor? What bad things could happen if you chose a DelayQueue for the ThreadPoolExecutor? (Make sure you work in the notions of the core and the maximum pool sizes in your answer.) Answer: An array blocking queue is good to use in cases where requests come in faster than they can be handled. In this case, an unbounded queue could grow to the point of exhausting system resources. Also, with array blocking queues, we can have a little more flexibility because the maximum pool size is actually useful! With unbounded queues, the maximum pool size is completely meaningless, since the executor will queue threads when corePoolSize threads are running and essentially never create new threads. DelayQueues are inherently unbounded and thus can cause system resource exhaustion when requests come in too fast. But system resource exhaustion can also occur when requests come in even at a moderate pace but the delays on each request are so long that they cannot be serviced until far in the future. This situation can also lead to the queue growing astronomically large. ------------------------------------------------------------------------------ PROBLEM 4 The classic producer-consumer problem can be implemented with a java.concurrent.util.Exchange object. A producer fills a plain old (unsynchronized) queue. A consumer starts with a plain old (unsynchronized) queue which is empty. When the producer's queue fills up, it exchanges with the consumer, who then processes each of the items. a) Assuming the plain old FIFO queue has methods add(Object) Object remove() isEmpty() isFull() and assuming the existence of methods Object produce() consume(Object) write the run() methods for the producer and consumer. Assume both methods have access to the same Exchanger object. b) Can this solution be easily generalized to a situation in which there is 1 producer but n consumers? Why or why not? (I'm looking for a kind of specific answer here....) Answer: // In Producer public void run() { PlainQueue queue = new PlainQueue(); try { while (queue != null) { queue.add(produce()); if (queue.isFull()) { queue = exchanger.exchange(queue); } } } catch (InterruptedException ignored) { } } // In Consumer public void run() { PlainQueue queue = new PlainQueue(); try { while (queue != null) { if (queue.isEmpty()) { queue = exchanger.exchange(queue); } consume(queue.remove()); } } catch (InterruptedException ignored) { } } The answer to this problem depends on you interpretation of the question. What kind of multi-consumer situation do we have? (1) Are all consumers interchangeable, meaning that any one of the consumers can consume and it doesn't matter which? (2) Is each product we are producing destined for a particular consumer? (3) Are we supposed to *broadcast* to all consumers? The naive generalization to n consumers in case (1) would give us a system with zillions of exchanges of empty queues between consumers. Making this work would require consumers to exchange ONLY with a producer. I'm not sure how easy that would be to implement. It would probably need another synchronization object, which crosses *my* line for being "easy". For case (2), we require a queue and an exchanger for each consumer. Each product is placed in the proper queue. Not bad. Case (3) is just like (2), except that each product produced goes into every queue. ------------------------------------------------------------------------------ PROBLEM 5 Write a beautiful three paragraph essay describing one distributed technology you mastered in your term project or term paper this semester. You must include: * what you used this technology for * how it was better than, or worse than, a competing technology. The essay has to be properly structured, with a good introductory sentence, a powerful sentence to close the first paragraph, and a decent summary in the last paragraph with a nod toward what kind of improvements can be envisioned in the technology you chose, and how that would help or hinder. Answer: Varies.