Contents Semaphores Introduction Binary Semaphore Counting

advertisement
Contents
Semaphores
Introduction
Semaphores were first defined by Dijkstra in the 1960's.
They are used to implement mutual exclusion and scheduling control.
Semaphores have a few differences from locks.
First, the constructor requires the specification of the number of permits to be granted.
There are also methods that return the number of total and free permits.
This class implements only a grant and release algorithm; unlike the Lock interface, no attached condition variables are available with
semaphores.
There is no concept of nesting; multiple acquisitions by the same thread acquire multiple permits from the semaphore.
There are generally two types of semaphores, binary and counting semaphores - but in reality, they are both the same. A binary
semaphore is a special kind of counting semaphore.
Binary Semaphore
Dijkstra used the following two functions to implement a semaphore:
semaphore.P()
Wait for semaphore to be greater than zero, then decrement by one.
“Test” in Dutch (proberen)
semaphore.V()
Increment semaphore by one and wake one waiting process.
“Increment” in Dutch (verhogen)
Using a semphore where the semaphore value is 0 or 1:
The following compares a binary semaphore with a lock:
Lock lock = new Lock();
lock.acquire().
balance += amount;
lock.release();
Semaphore s = new Semaphore(1);
s.P().
balance += amount;
s.V()
Here's one version of a binary semaphore:
public class Semaphore {
private boolean permit = true;
// permit is available
public synchronized void acquire() throws InterruptedException{
while(!permit) wait();
permit = false;
}
public synchronized void release() {
permit = true;
notify();
}
}
By throwing the exception, we don't need a try/catch in the code above. Instead, the client code has to provide the try/catch.
To compare the use of a binary semaphore with the use of synchronized methods, check out the BinarySemaphores project from the CVS
server.
Counting Semaphore
A counting semaphore is used to control the number of activities that can access a certain resource or perform a given action at the same
time.
An implementation of a counting semaphore in Java:
public class Semaphore {
/**
* The current count, which must be non-negative.
* permits available
*/
protected int count;
This is the number of
/**
* Create a counting Semaphore with a specified initial count.
*
* @param initCount The initial value of count.
*/
public Semaphore(int initCount)
count = initCount;
}
/**
* Subtract one from the count. Since count must be non-negative, wait until
* count is positive before decrementing it.
*
* @throws InterruptedException if thread is interrupted while waiting.
*/
public synchronized void down()
//could call this acquire()
throws InterruptedException {
while (count == 0) wait();
count--;
}
/**
* Add one to the count. Wake up a thread waiting to "down" the semaphore, if any.
*/
public synchronized void up() {
// could call this release()
count++;
notify();
}
}
util.concurrency
Study the Java API for the Semaphore class in java.util.concurrency.
Bounded Semaphore
Study the following code. What's happening?
public class BoundedSemaphore {
private int signals = 0;
private int bound
= 0;
public BoundedSemaphore(int upperBound){
bound = upperBound;
}
public synchronized void acquire() throws InterruptedException{
while(signals == 0) wait();
signals--;
}
public synchronized void release() throws InterruptedException{
while(signals == bound) wait();
signals++;
notify();
}
}
These methods throw an exception. Thus client code that uses acquire and release must use try/catch blocks.
Case Study: Parking Lot
A parking lot can store up to 20 cars. Cars arrive randomly and depart independently of arrivals. Threads are used to represent arrivals and
departures (we'll use random numbers to determine when cars arrive and leave).
Since we have a limit of 20 cars, we'll use a Semaphore (from the concurrency package) to keep arrivals from overfilling the lot.
Two implementations are presented. The first has not been properly designed and leads to errors. The second corrects this problem.
The code is found on the CVS server, project ParkingLot. Before focusing on the code, follow the design of the Arrivals and Departures
threads below.
1) The Buggy version
The semaphore is used to keep track of available spaces in the parking lot. It is set to 20. As cars enter the lot, the semaphore is
acquired, thus decreasing the number of permits allowed for "entry."
The Arrivals and Departures threads -
Arrivals: Instead of using an infinite loop in run(), we count the number of cars introduced in the simulator and allow up to 50.
public void run() {
Random rangen = new Random( );
while (count <50) {
if (rangen.nextInt(100) < 5)
// the next int is in the range 0-99
{
p.arrive();
// p is a ParkingLot object (actually, the buggy version).
count++;
}
}
}
Departures: The odds of having a departure are slightly smaller than having an arrival. This helps in creating a buildup of arrivals.
public void run() {
Random rangen = new Random( );
while (count < 50) {
if (rangen.nextInt(100) < 4)
{
p.depart();
count++;
}
}
}
// the next int is in the range 0-99
The class ParkingLotBug implements the the parking lot control using one counting semaphore that is initialized to 20. Open the code
files and explore the design of ParkingLotBug.
The problem (bug)
The problem with this version of the parking lot control is that while the semaphore prevents more than 20 cars from using the lot, it
does not prevent cars from leaving when the lot is empty.
2) The correct version
The trick is to introduce a second semaphore that keeps track of the number of used spaces in the lot. As cars leave the lot, the
semaphore subtracts from the number of "exit" permits. It is initialized to 0, since the lot is empty at first. As cars enter the lot, this
semaphore is released, thus adding to the number of cars that are parked.
An alternate approach is to do the following:
For newly parked cars, we'll keep track of the time they remain, having assigned a random park time (how long they will stay). When their
time is up, they depart. To implement this, we need to study the next section of the textbook - signals. We want to use a thread to keep
track of time. With each passing minute, the thread signals other threads of the passage of time.
We'll return to this problem once we've studied signals.
Signals
Introduction
Before exploring the information in the textbook, we begin with a study of signals and issues that arise.
The purpose of thread signaling is to enable threads to send signals to each other.
Additionally, thread signaling enables threads to wait for signals from other threads. For example, thread B might wait for a signal from thread
A indicating that data is ready to be processed.
The first set of pages on Signals present a study of defining signals and issues that may crop up when using the sample code.
Signaling via Shared Objects
In the next couple of pages, we use "tricks" that were introduced in past lectures. The MySignal class, below, is similar to some past
examples (like a counter object that provides synchronized methods for reading and incrementing the counter variable).
However the intent is different. MySignal is used merely as a "gate" which a client must pass through. The ability to pass through this gate is
a signal of sorts.
In this example, we use a shared object:
public class MySignal{
protected boolean hasDataToProcess = false;
public synchronized boolean hasDataToProcess(){
return this.hasDataToProcess;
}
public synchronized void setHasDataToProcess(boolean hasData){
this.hasDataToProcess = hasData;
}
}
There's nothing special about this approach - it just uses synchronized code for a writer and reader of the shared boolean.
The next page discusses an issue with using MySignal.
Busy Wait
If a thread needs to process data and is waiting for the data to become available, it might be executing the following loop:
protected MySignal sharedSignal = ...
...
while(!sharedSignal.hasDataToProcess()){
//do nothing... busy waiting
}
Unfortunately, the while loop keeps executing until hasDataToProcess() returns true. This is called busy waiting. The thread is busy while
waiting.
It's better to rely on wait() so that the thread can give up the lock and let others do their work.
Using wait())_ and notify()/notifyAll()
This is old stuff, applied to the current problem.
Busy waiting is not a very efficient utilization of the CPU in the computer running the waiting thread, except if the average waiting time is
very small. Instead, we can modify MySignal into MyWaitNotify by replacing hasDataToProcess() and setHasDataToProcess():
public class MyWaitNotify{
public void synchronized doWait(){
try{
wait();
} catch(InterruptedException e){...}
}
public void synchronized doNotify(){
notify();
}
}
But all is not perfect The trouble with the code above is that a listener, the object waiting to be signaled, may call doWait just after a signal is sent (by calling
doNotify). In this situation, there is a missed signal.
The following code avoids losing signals public class MyWaitNotify2{
boolean wasSignalled = false;
public void synchronized doWait(){
if(!wasSignalled){
try{
wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
public void synchronized doNotify(){
wasSignalled = true;
notify();
}
}
Signals (textbook)
Class Diagrams
Your textbook introduces a particular hierarchy of classes and interfaces:
2 PersistentSignal Examples
A persistent signal remains set until a single thread has received it.
1) A signal generated by the class that sends the signal.
The disk controller creates a new signal whenever a client calls asynWrite(). The text isn't very clear in this situation as to who initiates
the send() signal - it must be assumed that the disk controller does somewhere in its code. Also, if more than one thread calls
asynWrite(), the controller loses its reference to the first created PersistentSignal. We assume that there is only one client (the operating
system) that ever calls asynWrite().
In the second client code, a call to watch() is made, instead of waitS().
2) In this situation, the signal is created by the client.
Code - the following code compresses the text's hierarchy of classes.
public class PSignal {
private boolean arrived;
public PSignal()
{
arrived = false;
}
public synchronized void waitS() throws InterruptedException
{
while (!arrived) wait();
arrived = false;
}
public synchronized void send()
{
arrived = true;
notifyAll();
}
}
Transient signals
A transient signal is lost if no threads are waiting.
Releasing a single thread:
Releases all threads (called a Pulse)
The differences from above are:
send() is now defined as sendAll()
{
if (waiting > 0) {
arrived = true;
notifyAll();
}
}
waitS() is as above, however, just after the while loop, have ...
if (waiting == 0)
arrived = false;
And the catch block is:
if (--waiting == 0)
throw ie;
arrived = false;
Case Study
We've discussed ways of triggering events after fixed time intervals. Now we can take advantage of signals to define a Clock class that
sends signals to other threads.
What we'll do is create a "proof of concept" project that provides the basic structure for such a Clock.
As we design the project, your instructor will gather student ideas.
We begin by discussing the following The Clock class will signal other threads. Does the Clock class need to be a thread?
We begin by sketching out the Clock class; have we already seen a class that must loop, watching for a period of time to pass before
doing something? (How about the stock market project?) Review the code in that example.
How might we design the "other" threads? Remember, we are designing a proof of concept case study, so we don't want the other
threads to do a lot of processing that isn't important to us.
What would you expect to find in the main() method to test this project?
We'll use a persistent signal. We saw two different ways of introducing persistent signals - 1) create a signal in the sender object (the
Clock in this case study), and 2) in the waiting threads. In this project, we'll assume main() creates one signal and passes it to each of
the threads (and Clock).
Now we'll study the code stored on the CVS server - the PSignal project.
Run the program - what happens?
A review of the case study PSignal has a problem - the signal only reaches one waiter each time interval. When that waiter gets past the wait() loop in waitS, the
signal is set back to false.
We now study another approach - PSignalList. Again this is found on the CVS server.
In this study, the clock must maintain a list of signals and trigger them all during each time interval. This puts the task of maintenance on
the clock. Another approach is similar to PSignal, the clock just has to worry about one signal, as do each of the waiters. It's the signal
object that must do the work of making sure each waiter gets the signal.
The remaining case studies in the text.
The signals section (5.2) was complex due to the various classes defined in a hierarchy. There were also two types of signals considered:
persistent signals that remain set until a thread receives it, and transient signals that are lost if no threads are currently waiting.
In contrast, the next set of case studies are relatively simple.
Topics:
5.3 Events
5.4 Buffers (a review of the bounded buffer)
5.5 Blackboard
5.6 Broadcast
5.6.1 Multicast to a group
5.7 Barriers
Events
Background - enum
From SUN's web site:
An enum type is a type whose fields consist of a fixed set of constants. Common examples include compass directions (values of NORTH, SOUTH,
EAST, and WEST) and the days of the week.
Because they are constants, the names of an enum type's fields are in uppercase letters.
In the Java programming language, you define an enum type by using the enum keyword. For example, you would specify a days-of-the-week enum
type as:
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
An event is a bivalued variable (UP or DOWN). A thread can wait for an event to be set or reset.
(Note: The text uses an int parameter in await() and return an int from state(); these result in syntax errors in Eclipse.)
public class Event {
public enum EventState {UP, DOWN};
protected EventState
value;
public Event(EventState
value = initial;
}
initial) {
public synchronized void await(EventState state)
throws InterruptedException {
while (value != state) wait();
}
public synchronized void set() {
value = EventState.UP;
notifyAll();
}
public synchronized void reset() {
value = EventState.DOWN;
notifyAll();
}
public synchronized void toggle() {
value = (value == EventState.DOWN ? EventState.UP : EventState.DOWN);
notifyAll();
}
public synchronized EventState
return value;
}
state() {
}
Note: to use an EventState value outside the class you would use for example:
Event.EventState.DOWN
Compare this code to that given in the binary semaphore.
Specifically ...
await() wrt acquire()
Buffers
Review the code in the text - we've seen this before.
Recall the use of generics in Java.
public class BoundedBuffer<Data> {
Public BoundedBuffer( int length) {
...
}
...
Public synchronized void put (Data item)
... {
...
}
...
}
Instantiating a buffer:
BoundedBuffer<String>
bb = new BoundedBuffer<String>(25); // 25 = parameter, length.
bb.put("Mary Smith"); // put requires a String as parameter.
A quick test project.
Using Eclipse, we'll write a short project that includes a class that stores one datum using a generic. We can use the toString() method in
the class to output the datum's value.
public class Storage<Data>
{
...
}
Blackboard
This structure is similar to an Event but data is also transferred.
This is similar to the events abstraction with data transfer or the buffer abstraction with a nondestructive read and facility to invalidate the
data.
Here's sample code:
public class Blackboard<Data> {
private Data theMessage;
private boolean statusValid;
public Blackboard()
{
statusValid = false;
}
public Blackboard(Data initial)
{
theMessage = initial;
statusValid = true;
}
public synchronized void write(Data message)
{
theMessage = message;
statusValid = true;
notifyAll();
}
public synchronized void clear()
{
statusValid = false;
}
public synchronized Data read()
throws InterruptedException
{
while (!statusValid) wait();
return theMessage;
}
public boolean dataAvailable()
{
return statusValid;
}
}
Applying the class:
Scenario:
Recall the Clock class. We'd like the clock to send its "lastTime" value to main() every 5 seconds.
Questions:
1) What classes would Main have to create? (We assume Main is the class that contains main().)
2) How does Clock have to be edited to work in this case study?
The old code for Clock -
public class Clock extends Thread {
private long lastTime;
private PSignal ps;
public Clock(PSignal s)
{
lastTime = System.currentTimeMillis();
ps = s;
}
public void run()
{
while (true)
{
long time = System.currentTimeMillis();
if (time - lastTime > 2000)
{
lastTime = time;
ps.send();
}
}
} //run
}
Check out the Blackboard project from CVS and explore the Main and Clock classes. Focus on the casting used in the project.
Now run the program. Result This seems to run correctly. Now uncomment the commented block in main() and run again.
Discussion - what happened and why is this not what we'd expect?
A class discussion: (To be conducted after the reading assignment.)
Simpler form of a blackboard Your text discusses a blackboard that does not have a clear operation; all data is preserved until overwritten.
Read would be nonblocking and hence would not throw the InterruptedException.
Discuss this - (how would Read be defined? Why no interrupt?)
In Eclipse, update the Blackboard project and focus on SimpleBlackboard. Let's edit the file to reflect the ideas above.
Broadcast
Review - Pulse
A Pulse is a transient signal that allows all waiting threads to be released.
Here's code for a Pulse that combines the text's hierarchy of classes used to define signals.
/**
* Pulse is a transient signal (lost if no threads are waiting)
* that allows all waiting threads to be released.
*/
public class Pulse {
protected int waiting = 0;
protected boolean arrived = false;
/**
* Pulse releases all waiting threads
*/
public synchronized void sendAll()
{
if (waiting > 0)
{
arrived = true;
notifyAll();
}
}
public synchronized void waitS()
throws InterruptedException
{
try
{
while (!arrived)
{
waiting++;
wait();
waiting--;
}
// The last thread to finish sets arrived to false
if (waiting == 0) arrived = false;
} catch (InterruptedException ie)
{
if (--waiting == 0) arrived = false;
throw ie;
}
}
}
A Broadcast is similar to a pulse except that data is sent.
Code:
public class Broadcast<Data> {
private Data theMessage;
private boolean arrived;
private int waiting;
public Broadcast()
{
arrived = false;
waiting = 0;
}
public synchronized void send(Data message)
{
if (waiting != 0 && !arrived)
{
theMessage = message;
arrived = true;
notifyAll();
}
}
public synchronized Data receive()
throws InterruptedException
{
try
{
while (!arrived)
{
waiting++;
wait();
waiting--;
}
if (waiting == 0)
arrived = false;
} catch (InterruptedException ie)
if (--waiting == 0)
arrived = false;
return theMessage;
}
}
Multicast - a special type of Broadcast
A Muticast is used to send data to a specific group of threads.
Rules:
All threads in the group should receive the data (not just waiting threads) when data is sent.
Only when all threads have received the data will more data be allowed to be sent.
Implications:
A Multicast (which acts as the middleman between sending threads and receiving threads) must maintain a list of references to the
receiving threads, which means it should provide a way to
let threads join the group of receiving threads,
let threads leave the group of receiving threads.
Code
The text's code example is rich in the use of exceptions. Be sure to read all of it; it's worth it!
We begin with the class' variables and constructor.
The array, activeThreads, lists the receiving threads. The boolean array is used to tell which threads received the message.
public class Multicast<D> {
private int size;
private Thread[] activeThreads;
private boolean[] receivedMessage;
private D theMessage;
public Multicast(int groupSize)
{
size = groupSize;
activeThreads = new Thread[size];
receivedMessage = new boolean[size];
for (int i=0; i<size; i++)
{
receivedMessage[i] = true;
activeThreads[i] = null;
}
}
Next we look at the join() method. Here's where the exceptions start to appear. The second code snippet shows one exception class very simple to do. In fact, if you write the join() code, below, Eclipse offers to create the exception class (much like it offers to insert trycatch block when one is missing).
public synchronized void join()
throws GroupFullException, AlreadyInGroupException
{
int j = size;
for (int i=0; i<size; i++)
{
if (activeThreads[i] == null)
{
j = i;
break;
}
if (activeThreads[i] == Thread.currentThread())
throw new AlreadyInGroupException();
}
// j is now first free slot.
if (j == size) throw new GroupFullException();
activeThreads[j] = Thread.currentThread();
receivedMessage[j] = true;
}
public class AlreadyInGroupException extends Exception {
}
Continued - Multicast
We'll look at one more set of methods and leave the rest for students to read (section 5.6.1 in your text)
Here's the receive() method. It relies on several support methods.
public synchronized D receive()
throws NotInGroupException, InterruptedException
{
while(alreadyReceived()) wait();
logReceived();
if (allReceived())
notifyAll();
return theMessage;
}
The logReceived() method:
private void logReceived()
throws NotInGroupException
{
receivedMessage[findThreadIndex()] = true;
}
findThreadIndex is another often used internal method (private). Note that i must be declared before the for loop, since it is used again
in the if. Using code like:
for (int i = 0; ........) { /* body of loop */ }
defines the scope of i to be the body only.
private int findThreadIndex()
throws NotInGroupException
{
int i;
for (i=0; i<size; i++)
if (activeThreads[i] == Thread.currentThread())
break;
if (i==size) throw new NotInGroupException();
return i;
}
allReceived determines whether all threads received the message:
private boolean allReceived()
{
boolean done = true;
for (int i=0; i<size; i++)
{
if (activeThreads[i] != null &&
!receivedMessage[i])
{
done = false;
break;
}
}
return done;
}
Barriers
A barrier is used to make threads wait until all relevant threads have reached the barrier.
For threads that haven't reached the barrier - they will continue doing whatever they're supposed to be doing.
For threads that have done what they have to do and now need to wait for late threads, they wait.
The Barrier class has one important method, waitB(),:
public synchronized void waitB()
throws InterruptedException
{
while (releasing) wait();
// releasing initially set to false
try
{
arrived++;
while (arrived != need && !releasing)
wait();
if (arrived == need)
{
releasing = true;
notifyAll();
}
} finally
{
// need = number of threads involved
arrived--;
if (arrived == 0)
{
releasing = false;
notifyAll();
}
}
}
About finally:
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception
occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally
bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are
anticipated.
Consider the following type of computation:
double val = (..... lengthy calculation ....) + (..... lengthy calculation ....)
We could spawn two threads to do the two calculations. When they are done, they are added.
Open Eclipse and look at the Barrier project. Look at the three classes involved. Run the program.
To test the effectiveness of the project, comment out the try-catch that calls waitB() in the main method. Run again.
Discussion of Communication Paradigms
Starting line
For each of the following scenarios (except the first), what types of paradigms might we consider and how to apply? Note that paradigms
that include data (messages) could also be used. In such cases, the data would be inconsequential.
We've seen that a barrier blocks threads until all have arrived.
How about a starting line that does not let any thread begin until the starting signal; late threads start when they make it to the line.
How about a starting line that lets threads begin when the starting signal is sounded and lets late threads go until at some point no
additional threads are permitted to begin, until another starting signal is sounded?
What if as long as there are threads starting or getting ready to start the race, a new thread is allowed in the race, all later threads are
not allowed?
What if a race times an individual thread, thus no more than one runner (thread) is allowed on the course at a time - other threads must
wait their turn?
What if a race allows only 3 threads to run at once?
Messages
A message center sends a notice to employees. Which paradigms would you use in the following scenarios.
You want all employees to get the notice.
The notice is time-sensitive only employees looking out for the notice will get it.
Other Concurrency Topics
The Dining Philosophers
[Invented by Dijkstra] Five philosophers are sitting around a round table. Each of the five philosophers has a bowl of rice, and between every
two bowls there is a single chopstick (or we could consider spaghetti and forks).
Philosophers never talk to each other. They spend their time thinking. When a philosopher becomes hungry, he attempts to pick up the two
chopsticks closest to him (the ones that are shared between him and his left and right neighbors). Each philosopher can only pick up one
chopstick at a time. Then he eats, without putting down the chopsticks. When he is done, he puts both chopsticks down and resumes thinking.
Analysis
How do we write a threaded program to simulate philosophers? First, we notice that these philosophers are in a thinking-picking up
chopsticks-eating-putting down chopsticks cycle as shown below.
The "pick up chopsticks" part is the key point. How does a philosopher pick up chopsticks? Well, in a program, we simply print out
messages such as ``Have left chopsticks'', which is very easy to do.
Simple pseudo code (and incorrect):
void philosopher() {
while(true) {
sleep();
get_left_fork();
get_right_fork();
eat();
put_left_fork();
put_right_fork();
}
}
The problem is each chopstick is shared by two philosophers and hence a shared resource. We certainly do not want a philosopher to pick
up a chopstick that has already been picked up by his neighbor. This is a race condition.
To address this problem, we may consider each chopstick as a shared item protected by a mutex lock. Each philosopher, before he can
eat, locks his left chopstick and locks his right chopstick. If the acquisitions of both locks are successful, this philosopher now owns two
locks (hence two chopsticks), and can eat. After finishing eating, this philosopher releases both chopsticks, and thinks. This execution flow
is shown below.
Because we need to lock and unlock a chopstick, each chopstick is associated with a mutex lock. Since we have five philosophers who
think and eat simultaneously, we need to create five threads, one for each philosopher. Since each philosopher must have access to the
two mutex locks that are associated with its left and right chopsticks, these mutex locks are global variables.
Problems:
The philosophers never speak to each other, which creates a dangerous possibility of deadlock when every philosopher holds a left fork and
waits perpetually for a right fork (or vice versa).
Starvation might also occur independently of deadlock if a philosopher is unable to acquire both forks due to a timing issue. For example
there might be a rule that the philosophers put down a fork after waiting five minutes for the other fork to become available and wait a further
five minutes before making their next attempt.
An applet:
http://www.doc.ic.ac.uk/~jnm/concurrency/classes/Diners/Diners.html
Solutions
Resource hierarchy solution [Dijkstra's Solution]
One solution is to order the forks and require the philosophers to pick the forks up in increasing order. In this solution the philosophers
are labeled P1, P2, P3, P4, and P5 and the forks are likewise labeled F1, F2, F3, F4, and F5. The first philosopher (P1) will pick up the
first fork (F1) before he is allowed to pick up the second fork (F2). Philosophers P2 through P4 will behave in a similiar fashion, picking
up the fork Fx before picking up fork Fx+1. Philosopher P5 however picks up fork F1 before picking up fork F5. This change in behavior
for P5 creates an asymmetry that prevents deadlock.
Waiter Solution
A simple solution is achieved by introducing a waiter at the table. Philosophers must ask his permission before taking up any forks.
Because the waiter is aware of which forks are in use, he is able to arbitrate and prevent deadlock. When four of the forks are in use, the
next philosopher to request one has to wait for the waiter's permission, which is not given until a fork has been released. The logic is
kept simple by specifying that philosophers always seek to pick up their left hand fork before their right hand fork (or vice versa).
Using a golden chopstick
The following is from the web, and the description is not very clear. The pseudo code below is also a bit fuzzy, but it does provide an
answer regarding how the gold chopstick moves around the table.
One of the chopsticks is marked as gold, while the rest are wood. No philosopher is allowed to hold the gold stick without eating. This
prevents the philosophers from deadlocking.
The philosophers picks up one chopstick then the other. If the gold stick is picked up first, it is put down and the other stick is picked up.
It is possible that in the time between putting down the gold chopstick and picking up the other chopstick, all the other philosophers will
have eaten and moved the gold chopstick all the way around the table, so when the philosopher picks up the other chopstick, it too is the
gold chopstick. If this happens, the philosopher starts over. The solution is as follows:
Think
Pick up right chopstick
If right chopstick is gold
Drop right chopstick
Pick up left chopstick
If left chopstick is gold
Start over
Pick up right chopstick
Else
Pick up left chopstick
Eat
Switch chopsticks in hands
Drop right chopstick
Drop left chopstick
Repeat
The chopsticks are switched when the philosopher puts down the chopsticks. This allows the philosophers equal chances to eat.
Otherwise, the philosopher to the left of the gold chopstick would be disadvantaged.
Code:
class Philosopher extends Thread
{
final int ThinkTime=5000, EatTime=3000;
Chopstick rightStick,leftStick;
ScenePainter painter;
int rightHand,leftHand;
int myNum;
public Philosopher(Chopstick right, Chopstick left,
ScenePainter p, int n)
{
painter=p;
myNum=n;
rightStick=right;
leftStick=left;
start();
}
public void run()
{
for(;;)
{
think();
PickUpSticks();
eat();
PutDownSticks();
}
}
void think()
{
painter.drawThink(myNum);
try {sleep((int)(ThinkTime*Math.random()));}
catch (InterruptedException e) {}
painter.drawThink(myNum);
}
void PickUpSticks()
{
for(boolean gotem=false;!gotem;)
{
painter.drawFirstGrab(myNum);
rightHand=rightStick.grabStick();
painter.drawSecondGrab(myNum);
if(rightHand==Chopstick.gold)
{
painter.drawGoldGrab(myNum);
rightStick.dropStick(rightHand);
leftHand=leftStick.grabStick();
if(leftHand==Chopstick.gold)
{
leftStick.dropStick(leftHand);
continue; /* gold stick went around table */
}
rightHand=rightStick.grabStick();
painter.drawGoldGrab(myNum);
}
else leftHand=leftStick.grabStick();
painter.drawSecondGrab(myNum);
painter.drawFirstGrab(myNum);
gotem=true;
}
}
void eat()
{
painter.drawEat(myNum);
try {sleep((int)(EatTime*Math.random()));}
catch (InterruptedException e) {}
painter.drawEat(myNum);
}
void PutDownSticks()
{/* swap sticks and put them down */
rightStick.dropStick(leftHand);
leftStick.dropStick(rightHand);
}
}
Download