Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Concurrent & Distributed Computing PRACTICAL 6 – Monitors Note. A monitor is a synchronization mechanism for processes in a shared environment. In this practical we simulate monitors using Java classes. Question 1 – Monitors in Java There are many interesting situations where separate, concurrently running threads do share data and must consider the state and activities of other threads. One such set of programming situations are known as producer/consumer scenarios where the producer generates a stream of data which then is consumed by a consumer. For example, you can imagine a Java application where one thread (the producer) writes data to a file while a second thread (the consumer) reads data from the same file. Or, as you type characters on the keyboard, the producer thread places key events in an event queue and the consumer thread reads the events from the same queue. Both of these examples use concurrent threads that share a common resource: the first shares a file, the second shares an event queue. Because the threads share a common resource, they must be synchronized in some way. Consider the following example: The Producer generates an integer between 0 and 9 (inclusive), stores it in a "CubbyHole" object, and prints the generated number for user confirmation. To make the synchronization problem more interesting, the Producer sleeps for a random amount of time between 0 and 100 milliseconds before repeating the number generating cycle. The Consumer, being ravenous, consumes all integers from the CubbyHole (the exact same object into which the Producer put the integers in the first place) as quickly as they become available. The Producer and Consumer in this example share data through a common CubbyHole object. And you will note that neither the Producer nor the Consumer make any effort whatsoever to ensure that the Consumer is getting each value produced once and only once. The synchronization between these two thread actually occurs via the get() and put() methods of the CubbyHole object. The code for the CubbyHole, Producer and Consumer is as follows: /* Class CubbyHole */ class CubbyHole { private int contents; // this is the condition variable. private boolean available = false; public synchronized int get() { 1 while (available == false) { try { wait(); } catch (InterruptedException e) { } // end while available = false; notify(); return contents; } // end get public synchronized void put(int value) { while (available == true) { try { wait(); } catch (InterruptedException e) { } // end while contents = value; available = true; notify(); } // end put } } } // end CubbyHole /* Producer */ class Producer extends Thread { private CubbyHole cubbyhole; public Producer(CubbyHole c) { cubbyhole = c; } // end constructor public void run() { for (int i = 0; i < 10; i++) { cubbyhole.put(i); System.out.println("Producer put: " + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } // end for } // end run } // end Producer 2 /* Consumer */ class Consumer extends Thread { private CubbyHole cubbyhole; public Consumer(CubbyHole c) { cubbyhole = c; } // end constructor public void run() { int value = 0; for (int i = 0; i < 10; i++) { try { sleep((int)(Math.random() * 2000)); } catch (InterruptedException e) { } value = cubbyhole.get(); System.out.println("Consumer got: " + value); } // end for } // end run } // end Consumer Use the above classes in a main program (such as below) to demonstrate the Producer/Consumer activity in action: class ProducerConsumerTest { public static void main(String[] args) { CubbyHole c = new CubbyHole(); Producer p1 = new Producer(c); Consumer c1 = new Consumer(c); p1.start(); c1.start(); } // end main } // end ProducerConsumerTest Note the following: The CubbyHole class for the Producer/Consumer example has two synchronized methods: the put() method, which is used to change the value in the CubbyHole, and the get() method, which is used to retrieve the current value. Thus the system associates a unique monitor with every instance of CubbyHole. The CubbyHole has two private variables: contents, which is the current contents of the CubbyHole, and the boolean variable available, which indicates whether the CubbyHole contents can be retrieved. When available is true the Producer has just put a new value in the CubbyHole and the Consumer has not yet consumed it. The Consumer can consume the value in the CubbyHole only when available is true. Because CubbyHole has synchronized methods, Java provides a unique monitor for each instance of CubbyHole (including the one shared by the Producer and the 3 Consumer). Whenever control enters a synchronized method, the thread that called the method acquires the monitor for the object whose method has been called. Other threads cannot call a synchronized method on the same object until the monitor is released. Question 2 – Bounded Buffer with Monitors Unfortunately, although wait() and notify() do the job that you wanted in the previous question, they don’t have any parameters, so you can’t straightforwardly write a monitor which uses more than one condition variable. (Note. Essentially wait() and notify() are using one implicit condition variable) The problem arises when you try to develop a Monitor based solution if we need more than one condition queue, for example in the Producer/Consumer problem (with a buffer of size N), we need two condition queues, namely “not_full” and “not_empty” as shown in the lecture notes.) Here we have to code the behaviour between the Producer and the Consumer explicitly, because we have only one implicit condition queue available in Java. We can develop a solution here using one (implicit) condition queue (and some additional coding), but for more complex behaviour is not very satisfactory. Test the following code, and run the Producer and Consumer at different speeds (using the sleep() feature to delay them). /* Class BoundedBuffer*/ class BoundedBuffer { private int SIZE = 10; private int buffer [] = new int[SIZE] ; private boolean writable = true; private boolean readable = false; private int readLocation = 0; private int writeLocation = 0; private int val; public synchronized int get() { while (!readable) { try { wait(); } catch (InterruptedException e) { } } writable = true; val = buffer[readLocation]; readLocation = (readLocation+1) % SIZE; if (readLocation==writeLocation) readable = false; notify(); return val; } // end get public synchronized void put(int value) { 4 while (!writable) { try { wait(); } catch (InterruptedException e) { } } buffer[writeLocation] = value; readable = true; writeLocation = (writeLocation+1) % SIZE; if (writeLocation==readLocation) writable = false; notify(); } // end put } // end BoundedBuffer /* ProducerForBB */ class ProducerForBB extends Thread { private BoundedBuffer bBuffer; public ProducerForBB(BoundedBuffer bb) { bBuffer = bb; } // end constructor public void run() { for (int i = 0; i < 20; i++) { bBuffer.put(i); System.out.println("Producer put: " + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } // end for } // end run } // end ProducerForBB /* ConsumerForBB */ class ConsumerForBB extends Thread { private BoundedBuffer bBuffer; public ConsumerForBB(BoundedBuffer bb) { bBuffer = bb; } // end constructor public void run() { int value = 0; for (int i = 0; i < 20; i++) 5 { try { sleep((int)(Math.random() * 200)); } catch (InterruptedException e) { } value = bBuffer.get(); System.out.println("Consumer got: " + value); } // end for } // end run } // end ConsumerForBB //main class to test the BoundedBuffer class BoundedBufferTest { public static void main(String[] args) { BoundedBuffer bbb = new BoundedBuffer(); ProducerForBB p1 = new ProducerForBB(bbb); ConsumerForBB c1 = new ConsumerForBB(bbb); p1.start(); c1.start(); } // end main } // end BoundedBufferTest Question 3 – Bounded Buffer with Monitors Compare this BoundedBuffer solution (in Question 2) with the Ambulance porter and the Ward porter solution you encountered in the lectures/practicals. Match up the behaviour of the Ambulance Porter and the Ward Porter with the solution of the Bounded Buffer problem with Monitors and develop a monitor based solution for the Ambulance/Ward Porter scenario and test it in Java code. Now make the Ambulance Porter work much harder than the Ward Porter, so that the treatment room fills up, and the Ambulance Porter must wait for the Ward Porter to remove patients to make room for new patients. Question 4 – Monitors with explicit multiple Condition queues for waiting In Java there is no keyword to directly create a monitor. To implement a monitor with multiple condition queues, you can create a class and use Lock and Condition classes (which are part of the java.util.concurrent library). Lock is the interface and ReentrantLock is the main used implementation of the Lock interface. In question 2 we considered a monitor based solution to the bounded buffer problem, 6 but the coding was more elaborate because it used “synchronised methods” with a single implicit condition variable. We now consider s solution that uses two Condition queues, which leads to a more natural solution that it closer to the earlier solution that was developed using semaphores. You can create conditions using the newCondition method on the lock. A condition is a variable of type Condition. You can make the current thread wait on the condition using the await method and you can signal threads using signal and signalAll methods. import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* Class BoundedBuffer*/ class BoundedBuffer { private int SIZE = 10; private int buffer [] = new int[SIZE] ; private int val; private int front; private int rear; private int count; private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); public int get() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); } val = buffer[front]; front = (front + 1) % SIZE; count--; notFull.signal(); return val; } finally { lock.unlock(); } } // end get public void put (int value) throws InterruptedException { lock.lock(); try { while (count == SIZE) { notFull.await(); } buffer[rear] = value; rear = (rear + 1) % SIZE; count++; notEmpty.signal(); 7 } finally { lock.unlock(); } } // end put } // end BoundedBuffer /* Consumer */ class Consumer extends Thread { private BoundedBuffer bBuffer; public Consumer (BoundedBuffer bb) { bBuffer = bb; } // end constructor public void run() { int value = 0; for (int i = 0; i < 20; i++) { try { value = bBuffer.get(); } catch (InterruptedException e) {e.printStackTrace();} System.out.println("Consumer got: " + value); } // end for } // end run } // end Consumer /* Producer */ class Producer extends Thread { private BoundedBuffer bBuffer; public Producer(BoundedBuffer bb) { bBuffer = bb; } // end constructor public void run() { for (int i = 0; i < 20; i++) { try { bBuffer.put(i); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println("Producer put: " + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } // end for 8 } // end run } // end Producer //main class to test the BoundedBuffer class BoundedBufferTest { public static void main(String[] args) { BoundedBuffer bb = new BoundedBuffer(); Producer p1 = new Producer(bb); Consumer c1 = new Consumer(bb); p1.start(); c1.start(); } // end main } // end BoundedBufferTest Test this implementation of the Bounded Buffer problem and note the use of the two Condition queues “notFull” and “notEmpty”. Also note that the two methods are protected with the lock to ensure mutual exclusion. Make the Producer produce values faster than the Consumer can take them so that the buffer fills up (i.e. get the Consumer to sleep for different periods of time using Thread.sleep(xxx); so that the Producer is running faster than the Consumer). END 9