/////////////////////////////////////////////////////////////////
//
// SWMRG.java
//
/**
* A Single Writer Multiple Reader Guard (SWMRG) class. By calling the
* startRead, endRead, startWrite and endWrite methods of a SWMRG object, a
* group of threads can synchronize their access to data. A SWMRG can thus be
* used to ensure that no readers are reading while a writer writes data (so
* readers don't get corrupt data) while still allowing multiple readers to
* read at the same time (so long as no writers are writing).
* @author Jonathan Locke
*/
public class SWMRG
{
/**
* The number of currently active readers
*/
protected int activeReaders = 0;
/**
* The number of currently active writers
*/
protected int activeWriters = 0;
/**
* The number of waiting readers
*/
protected int waitingReaders = 0;
/**
* The number of waiting writers
*/
protected int waitingWriters = 0;
/**
* The number of writes that have occurred since the last read
*/
protected int consecutiveWrites = 0;
/**
* The number of reads that have occurred since the last write
*/
protected int consecutiveReads = 0;
/**
* The number of writes that are allowed in a row (before giving readers a
* chance). This can be used to ensure that readers don't 'starve.'
*/
public int maxConsecutiveWrites = 4;
/**
* The number of reads that are allowed in a row (before giving writers a
* chance). This can be used to ensure that writers don't 'starve.'
*/
public int maxConsecutiveReads = 32;
/**
* Uses the variables above to determine if a reader should be allowed to
* access the shared data.
* @return True if a reader should be allowed right now
*/
protected boolean allowReader()
{
if (activeWriters == 0)
{
if (waitingWriters == 0)
{
return true;
}
return consecutiveReads < maxConsecutiveReads;
}
return false;
}
/**
* Uses the variables above to determine if a writer should be allowed to access
* the shared data.
* @return True if a writer should be allowed right now
*/
protected boolean allowWriter()
{
if (activeReaders == 0 && activeWriters == 0)
{
if (waitingReaders == 0)
{
return true;
}
return consecutiveWrites < maxConsecutiveWrites;
}
return false;
}
/**
* Call this method before attempting to read shared data. On returning you
* are guaranteed that no writers will change the shared data until you call
* endRead. This method waits until a reader is allowed (until allowReader
* returns true). At this point, there will be no active writers.
*/
public synchronized void startRead()
{
waitingReaders++;
while (!allowReader())
{
try { wait(); } catch (InterruptedException e) { }
}
waitingReaders--;
activeReaders++;
consecutiveWrites = 0;
if (waitingWriters > 0)
{
consecutiveReads++;
}
}
/**
* Call this method after reading shared data. This method decrements the
* number of active readers and notifies any waiting readers/writers.
*/
public synchronized void endRead()
{
activeReaders--;
notifyAll();
}
/**
* Call this method before attempting to change shared data. On returning you
* are guaranteed that no readers will attempt to read the shared data until
* you call endWrite. This method waits until a writer is allowed (until
* allowWriter returns true). At this point, there will be no active readers.
*/
public synchronized void startWrite()
{
waitingWriters++;
while (!allowWriter())
{
try { wait(); } catch (InterruptedException e) { }
}
waitingWriters--;
activeWriters++;
consecutiveReads = 0;
if (waitingReaders > 0)
{
consecutiveWrites++;
}
}
/**
* Call this method after changing shared data. This method decrements the
* number of active writers and notifies any waiting readers/writers.
*/
public synchronized void endWrite()
{
activeWriters--;
notifyAll();
}
}
Figure 3 TestSWMRG.java
/////////////////////////////////////////////////////////////////
//
// TestSWMRG.java
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* Test class to exercise/demonstrate the SWMRG class
* @author Jonathan Locke
*/
public class TestSWMRG extends Applet implements Runnable, ItemListener
{
static final int readers = 5; // Number of reader threads
static final int writers = 2; // Number of writer threads
static final int both = readers + writers; // Total number of threads
Thread thread[] = new Thread[both]; // Reader and writer threads
ThreadStatus t[] = new ThreadStatus[both]; // Status widgets for threads
SWMRG s = new SWMRG(); // Our SWMRG object
int n = 0; // Our SWMRG synchronized data
int readerSpeed = 500; // Speed of readers in ms.
int writerSpeed = 1000; // Speed of writers in ms.
int readerIdle = 500; // Idle of readers in ms.
int writerIdle = 1000; // Idle of writers in ms.
boolean randomize = false; // Should randomize delays
Choice choiceReaderSpeed = null; // Choice for reader delay
Choice choiceWriterSpeed = null; // Choice for writer delay
Choice choiceReaderIdle = null; // Choice for reader idle
Choice choiceWriterIdle = null; // Choice for writer idle
Checkbox checkRandomize = null; // Randomize delays
static final String strWriter = "Writer-"; // Writer prefix
static final String strReader = "Reader-"; // Reader prefix
static Random rnd = new Random();
static String[] delays = { "100", "200", "500", "1000", "2000", "3000" };
/**
* Create components and start threads
*/
public void init()
{
GridBagLayout gb = new GridBagLayout();
setLayout(gb);
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridwidth = 1;
c.weightx = 1.0;
c.anchor = c.WEST;
for (int i = 0; i < both; i++)
{
boolean isWriter = (i < writers);
String name = isWriter ? (strWriter + (i + 1)) : (strReader + (i -
writers + 1));
if (i == writers)
{
add(new Label(""), c);
}
add(t[i] = new ThreadStatus(name + ": Starting up", isWriter), c);
thread[i] = new Thread(this, name);
thread[i].start();
}
add(new Label(""), c);
add(new Label("Reader idle time (in milliseconds):"), c);
add(choiceReaderIdle = new Choice(), c);
add(new Label("Reading speed (in milliseconds):"), c);
add(choiceReaderSpeed = new Choice(), c);
add(new Label(""), c);
add(new Label("Writer idle time (in milliseconds):"), c);
add(choiceWriterIdle = new Choice(), c);
add(new Label("Writing speed (in milliseconds):"), c);
add(choiceWriterSpeed = new Choice(), c);
add(new Label(""), c);
add(checkRandomize = new Checkbox("Randomize delays"), c);
for (int i = 0; i < delays.length; i++)
{
choiceReaderSpeed.add(delays[i]);
choiceWriterSpeed.add(delays[i]);
choiceReaderIdle.add(delays[i]);
choiceWriterIdle.add(delays[i]);
}
choiceReaderIdle.select("500");
choiceWriterIdle.select("500");
choiceReaderSpeed.select("1000");
choiceWriterSpeed.select("1000");
choiceReaderSpeed.addItemListener(this);
choiceWriterSpeed.addItemListener(this);
choiceReaderIdle.addItemListener(this);
choiceWriterIdle.addItemListener(this);
updateValues();
}
/**
* A choice changed, so we need to update the delay values
* @param e Ignored
*/
public void itemStateChanged(ItemEvent e)
{
updateValues();
}
/**
* Return insets for applet
* @return Applet insets
*/
public Insets getInsets()
{
return new Insets(4, 10, 4, 10);
}
/**
* Get delay choices
*/
void updateValues()
{
readerSpeed = Integer.parseInt(choiceReaderSpeed.getSelectedItem());
writerSpeed = Integer.parseInt(choiceWriterSpeed.getSelectedItem());
readerIdle = Integer.parseInt(choiceReaderIdle.getSelectedItem());
writerIdle = Integer.parseInt(choiceWriterIdle.getSelectedItem());
randomize = checkRandomize.getState();
}
/**
* Read or write according to the rules
*/
public void run()
{
delay(3000);
while (true)
{
if (Thread.currentThread().getName().startsWith(strWriter))
{
write();
}
else
{
read();
}
}
}
/**
* Gets ThreadStatus UI component for current thread
*/
ThreadStatus getThreadStatus()
{
for (int i = 0; i < both; i++)
{
if (thread[i] == Thread.currentThread())
{
return t[i];
}
}
return null;
}
/**
* Sets the state of the current thread
* @param state New state
*/
void setState(int state)
{
getThreadStatus().setState(state);
switch (state)
{
case ThreadStatus.WAITING: say ("Waiting."); break;
case ThreadStatus.WRITING: say ("Writing."); break;
case ThreadStatus.READING: say ("Read " + n + "."); break;
case ThreadStatus.IDLE: say ("Idle."); break;
}
}
/**
* Say something for the current thread
*/
void say(String s)
{
getThreadStatus().say(Thread.currentThread().getName() + ": " + s);
}
/**
* Wait for a bit
* @param milliseconds How long to delay
*/
void delay(long milliseconds)
{
if (randomize)
{
milliseconds += (rnd.nextInt() % (milliseconds / 2));
}
try { Thread.sleep(milliseconds); } catch (InterruptedException e) {}
}
/**
* Perform a read operation
*/
void read()
{
setState(ThreadStatus.WAITING);
delay(100);
// Use SWMRG to wait until we can read
s.startRead();
setState(ThreadStatus.READING);
delay(readerSpeed);
// An odd number would indicate a bug in SWMRG.
if ((n % 2) == 1)
{
say("ERROR! (n = " + n + ")");
delay(100000);
}
// Done reading
s.endRead();
setState(ThreadStatus.IDLE);
delay(readerIdle);
}
/**
* Perform a write operation
*/
void write()
{
setState(ThreadStatus.WAITING);
delay(100);
// Use SWMRG to wait until we can write
s.startWrite();
setState(ThreadStatus.WRITING);
n++;
say("Starting write (n=" + n + ")");
delay(writerSpeed / 2);
n++;
say("Finishing write (n=" + n + ")");
delay(writerSpeed / 2);
// Done writing
s.endWrite();
setState(ThreadStatus.IDLE);
delay(writerIdle);
}
/**
* Frame class to hold applet
*/
class AppletFrame extends Frame
{
/**
* Constructor
* @param title Title of applet frame
*/
public AppletFrame(String title)
{
super(title);
// Add a window event listener (a nested-class adapter)
// to listen for window close events.
addWindowListener
(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}
);
}
}
/**
* Constructor
* @param arg Command-line arguments
*/
public TestSWMRG()
{
AppletFrame f = new AppletFrame("TestSWMRG");
f.setLayout(new FlowLayout());
f.add(this);
init();
f.pack();
f.show();
}
/**
* Main application entrypoint.
* @param arg Command-line arguments
*/
static public void main(String[] arg)
{
new TestSWMRG();
}
}
Figure 4 ThreadStatus.java
/////////////////////////////////////////////////////////////////
//
// ThreadStatus.java
//
import java.awt.*;
/**
* A simple custom component to show the status of a thread graphically.
* @author Jonathan Locke
*/
class ThreadStatus extends Canvas
{
public static final int IDLE = 0; // Thread is idle
public static final int READING = 1; // Thread is reading data
public static final int WRITING = 2; // Thread is writing data
public static final int WAITING = 3; // Thread is waiting
protected String str; // Current text to display
protected int state = IDLE; // State of thread
protected Image img = null; // Offscreen buffer
protected int dxImg = 0; // Width of image
protected int dyImg = 0; // Height of image
protected boolean isWriter = false; // True if thread is a writer
protected Color stateColor[] =
{
new Color(96, 96, 0), // IDLE color
new Color(0, 255, 0), // READING color
new Color(255, 0, 0), // WRITING color
new Color(255, 255, 0) // WAITING color
};
/**
* Constructor
* @param str String to display initially
* @param isWriter True if the thread plans to write data
*/
public ThreadStatus(String str, boolean isWriter)
{
say(str);
this.isWriter = isWriter;
}
/**
* Say something
* @param str What to say for the thread (since they can't talk)
*/
public void say(String str)
{
this.str = str;
repaint();
}
/**
* Set the state of the thread
* @param state New state to draw
*/
public void setState(int state)
{
this.state = state;
repaint();
}
/**
* Draw component
* @param g Graphics context to draw on
*/
public void paint(Graphics g)
{
int dx = getSize().width;
int dy = getSize().height;
if (img == null || dx > dxImg || dy > dyImg)
{
img = createImage(dxImg = Math.max(dx, dxImg), dyImg = Math.max(dy,
dyImg));
}
Graphics gm = img.getGraphics();
gm.setFont(getFont());
gm.setColor(Color.black);
gm.fillRect(0, 0, dx, dy);
Rectangle r = new Rectangle(4, 4, dy - 8, dy - 8);
gm.setColor(stateColor[state]);
gm.fillOval(r.x, r.y, r.width, r.height);
gm.setColor(isWriter ? Color.red : Color.green);
FontMetrics f = gm.getFontMetrics();
gm.drawString(str, r.x + r.width + r.width, dy - ((dy - f.getAscent()) / 2) -
f.getDescent());
g.drawImage(img, 0, 0, null);
}
/**
* Avoid flicker
* @param g Graphics context
*/
public void update(Graphics g)
{
paint(g);
}
/**
* Return preferred size of ThreadStatus component
* @return Preferred size
*/
public Dimension getPreferredSize()
{
Graphics g = getGraphics();
FontMetrics fm = g.getFontMetrics(getFont());
return new Dimension(fm.charWidth('X') * 40, fm.getHeight() + 4);
}
}