Download 6.Monitors

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Time value of money wikipedia , lookup

Security printing wikipedia , lookup

Transcript
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